/*******************************************************************************
 * Copyright (c) 2004, 2012 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.jdt.internal.core;

import org.eclipse.core.resources.IFolder;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.jdt.internal.core.search.AbstractSearchScope;
import org.eclipse.jdt.internal.core.search.JavaWorkspaceScope;
import org.eclipse.jdt.internal.core.search.indexing.IndexManager;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceChangeEvent;
import org.eclipse.core.resources.IResourceDeltaVisitor;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.ISafeRunnable;
import org.eclipse.core.runtime.PerformanceStats;
import org.eclipse.core.runtime.SafeRunner;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.jdt.core.ElementChangedEvent;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IElementChangedListener;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaElementDelta;
import org.eclipse.jdt.core.IJavaModel;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.compiler.CharOperation;
import org.eclipse.jdt.internal.compiler.SourceElementParser;
import org.eclipse.jdt.internal.core.builder.JavaBuilder;
import org.eclipse.jdt.internal.core.hierarchy.TypeHierarchy;
import org.eclipse.jdt.internal.core.util.Util;

import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;

/**
 * This class is used by <code>JavaModelManager</code> to convert
 * <code>IResourceDelta</code>s into <code>IJavaElementDelta</code>s.
 * It also does some processing on the <code>JavaElement</code>s involved
 * (e.g. closing them or updating classpaths).
 * <p/>
 * High level summary of what the delta processor does:
 * <ul>
 * <li>reacts to resource deltas</li>
 * <li>fires corresponding Java element deltas</li>
 * <li>deltas also contain non-Java resources changes</li>
 * <li>updates the model to reflect the Java element changes</li>
 * <li>notifies type hierarchies of the changes</li>
 * <li>triggers indexing of the changed elements</li>
 * <li>refresh external archives (delta, model update, indexing)</li>
 * <li>is thread safe (one delta processor instance per thread, see DeltaProcessingState#resourceChanged(...))</li>
 * <li>handles .classpath changes (updates package fragment roots, update project references, validate classpath (.classpath format,
 * resolved classpath, cycles))</li>
 * </ul>
 */
@SuppressWarnings({"rawtypes", "unchecked"})
public class DeltaProcessor {

    public static final  int          DEFAULT_CHANGE_EVENT   = 0; // must not collide with ElementChangedEvent event masks
    private final static int          IGNORE                 = 0;
    private final static int          SOURCE                 = 1;
    private final static int          BINARY                 = 2;
    private final static String       EXTERNAL_JAR_ADDED     = "external jar added"; //$NON-NLS-1$
    private final static String       EXTERNAL_JAR_CHANGED   = "external jar changed"; //$NON-NLS-1$
    private final static String       EXTERNAL_JAR_REMOVED   = "external jar removed"; //$NON-NLS-1$
    private final static String       EXTERNAL_JAR_UNCHANGED = "external jar unchanged"; //$NON-NLS-1$
    private final static String       INTERNAL_JAR_IGNORE    = "internal jar ignore"; //$NON-NLS-1$
    private final static int          NON_JAVA_RESOURCE      = -1;
    public static        boolean      DEBUG                  = false;
    public static        boolean      VERBOSE                = false;
    public static        boolean      PERF                   = false;
    /*
     * Used to update the JavaModel for <code>IJavaElementDelta</code>s.
     */
    private final        ModelUpdater modelUpdater           = new ModelUpdater();
    /*
     * Queue of deltas created explicily by the Java Model that
     * have yet to be fired.
     */
    public               ArrayList    javaModelDeltas        = new ArrayList();
    /*
     * Queue of reconcile deltas on working copies that have yet to be fired.
     * This is a table form IWorkingCopy to IJavaElementDelta
     */
    public               HashMap      reconcileDeltas        = new HashMap();
    /* A set of IJavaProject whose caches need to be reset */
    public               HashSet      projectCachesToReset   = new HashSet();
    /* A table from IJavaProject to an array of IPackageFragmentRoot.
     * This table contains the pkg fragment roots of the project that are being deleted.
     */
    public Map oldRoots;
    /*
     * Type of event that should be processed no matter what the real event type is.
     */
    public int overridenEventType = -1;
    /*
     * The Java model manager
     */
    JavaModelManager manager;
    /*
     * The global state of delta processing.
     */
    private DeltaProcessingState state;
    /*
     * The <code>JavaElementDelta</code> corresponding to the <code>IResourceDelta</code> being translated.
     */
    private JavaElementDelta     currentDelta;
    /* The java element that was last created (see createElement(IResource)).
     * This is used as a stack of java elements (using getParent() to pop it, and
     * using the various get*(...) to push it. */
    private Openable             currentElement;
    /*
     * Turns delta firing on/off. By default it is on.
     */
    private boolean isFiring = true;
    /*
     * Cache SourceElementParser for the project being visited
     */
    private SourceElementParser sourceElementParserCache;

    public DeltaProcessor(DeltaProcessingState state, JavaModelManager manager) {
        this.state = state;
        this.manager = manager;
    }

    /*
     * Answer a combination of the lastModified stamp and the size.
     * Used for detecting external JAR changes
     */
    public static long getTimeStamp(File file) {
        return file.lastModified() + file.length();
    }

    /*
     * Adds the dependents of the given project to the list of the projects
     * to update.
     */
    private void addDependentProjects(IJavaProject project, HashMap projectDependencies, HashSet result) {
        IJavaProject[] dependents = (IJavaProject[])projectDependencies.get(project);
        if (dependents == null) return;
        for (int i = 0, length = dependents.length; i < length; i++) {
            IJavaProject dependent = dependents[i];
            if (result.contains(dependent))
                continue; // no need to go further as the project is already known
            result.add(dependent);
            addDependentProjects(dependent, projectDependencies, result);
        }
    }

    /*
     * Adds the given child handle to its parent's cache of children.
     */
    private void addToParentInfo(Openable child) {
        Openable parent = (Openable)child.getParent();
        if (parent != null && parent.isOpen()) {
            try {
                OpenableElementInfo info = (OpenableElementInfo)parent.getElementInfo();
                // https://bugs.eclipse.org/bugs/show_bug.cgi?id=338006
                // Insert the package fragment roots in the same order as the classpath order.
                if (child instanceof IPackageFragmentRoot)
                    addPackageFragmentRoot(info, (IPackageFragmentRoot)child);
                else
                    info.addChild(child);
            } catch (JavaModelException e) {
                // do nothing - we already checked if open
            }
        }
    }

    private void addPackageFragmentRoot(OpenableElementInfo parent, IPackageFragmentRoot child)
            throws JavaModelException {

        IJavaElement[] roots = parent.getChildren();
        if (roots.length > 0) {
            IClasspathEntry[] resolvedClasspath = ((JavaProject)child.getJavaProject()).getResolvedClasspath();
            IPath currentEntryPath = child.getResolvedClasspathEntry().getPath();
            int indexToInsert = -1;
            int lastComparedIndex = -1;
            int i = 0, j = 0;
            for (; i < roots.length && j < resolvedClasspath.length; ) {

                IClasspathEntry classpathEntry = resolvedClasspath[j];
                if (lastComparedIndex != j && currentEntryPath.equals(classpathEntry.getPath())) {
                    indexToInsert = i;
                    break;
                }
                lastComparedIndex = j;

                IClasspathEntry rootEntry = ((IPackageFragmentRoot)roots[i]).getResolvedClasspathEntry();
                if (rootEntry.getPath().equals(classpathEntry.getPath()))
                    i++;
                else
                    j++;
            }

            for (; i < roots.length; i++) {
                // If the new root is already among the children, no need to proceed further. Just return.
                if (roots[i].equals(child)) {
                    return;
                }
                // If we start seeing root's classpath entry different from the child's entry, then the child can't
                // be present further down the roots array.
                if (!((IPackageFragmentRoot)roots[i]).getResolvedClasspathEntry().getPath()
                                                     .equals(currentEntryPath))
                    break;
            }

            if (indexToInsert >= 0) {
                int newSize = roots.length + 1;
                IPackageFragmentRoot[] newChildren = new IPackageFragmentRoot[newSize];

                if (indexToInsert > 0)
                    System.arraycopy(roots, 0, newChildren, 0, indexToInsert);

                newChildren[indexToInsert] = child;
                System.arraycopy(roots, indexToInsert, newChildren, indexToInsert + 1, (newSize - indexToInsert - 1));
                parent.setChildren(newChildren);
                return;
            }
        }
        parent.addChild(child);
    }

    /*
     * Process the given delta and look for projects being added, opened, closed or
     * with a java nature being added or removed.
     * Note that projects being deleted are checked in deleting(IProject).
     * In all cases, add the project's dependents to the list of projects to update
     * so that the classpath related markers can be updated.
     */
    private void checkProjectsAndClasspathChanges(IResourceDelta delta) {
//		IResource resource = delta.getResource();
//		IResourceDelta[] children = null;
//
//		switch (resource.getType()) {
//			case IResource.ROOT:
//				// workaround for bug 15168 circular errors not reported
//				this.state.getOldJavaProjecNames(); // force list to be computed
//				children = delta.getAffectedChildren();
//				break;
//			case IResource.PROJECT:
//				// NB: No need to check project's nature as if the project is not a java project:
//				//     - if the project is added or changed this is a noop for projectsBeingDeleted
//				//     - if the project is closed, it has already lost its java nature
//				IProject project = (IProject)resource;
//				JavaProject javaProject = (JavaProject)JavaCore.create(project);
//				switch (delta.getKind()) {
//					case IResourceDelta.ADDED:
//						this.manager.forceBatchInitializations(false/*not initAfterLoad*/);
//
//						// remember that the project's cache must be reset
//						this.projectCachesToReset.add(javaProject);
//
//						// workaround for bug 15168 circular errors not reported
//						if (JavaProject.hasJavaNature(project)) {
//							addToParentInfo(javaProject);
//							readRawClasspath(javaProject);
//							// ensure project references are updated (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=121569)
//							checkProjectReferenceChange(project, javaProject);
//							// and external folders as well
//							checkExternalFolderChange(project, javaProject);
//						}
//
//						this.state.rootsAreStale = true;
//						break;
//
//					case IResourceDelta.CHANGED:
//						if ((delta.getFlags() & IResourceDelta.OPEN) != 0) {
//							this.manager.forceBatchInitializations(false/*not initAfterLoad*/);
//
//							// remember that the project's cache must be reset
//							this.projectCachesToReset.add(javaProject);
//
//							// workaround for bug 15168 circular errors not reported
//							if (project.isOpen()) {
//								if (JavaProject.hasJavaNature(project)) {
//									addToParentInfo(javaProject);
//									readRawClasspath(javaProject);
//									// ensure project references are updated
//									checkProjectReferenceChange(project, javaProject);
//									// and external folders as well
//									checkExternalFolderChange(project, javaProject);
//								}
//							} else {
//								try {
//									javaProject.close();
//								} catch (JavaModelException e) {
//									// java project doesn't exist: ignore
//								}
//								removeFromParentInfo(javaProject);
//								this.manager.removePerProjectInfo(javaProject, false /* don't remove index files and timestamp info of
//								external jar */);
//								this.manager.containerRemove(javaProject);
//							}
//							this.state.rootsAreStale = true;
//						} else if ((delta.getFlags() & IResourceDelta.DESCRIPTION) != 0) {
//							boolean wasJavaProject = this.state.findJavaProject(project.getName()) != null;
//							boolean isJavaProject = JavaProject.hasJavaNature(project);
//							if (wasJavaProject != isJavaProject) {
//								this.manager.forceBatchInitializations(false/*not initAfterLoad*/);
//
//								// java nature added or removed: remember that the project's cache must be reset
//								this.projectCachesToReset.add(javaProject);
//
//								// workaround for bug 15168 circular errors not reported
//								if (isJavaProject) {
//									addToParentInfo(javaProject);
//									readRawClasspath(javaProject);
//									// ensure project references are updated (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=172666)
//									checkProjectReferenceChange(project, javaProject);
//									// and external folders as well
//									checkExternalFolderChange(project, javaProject);
//								} else {
//									// remove classpath cache so that initializeRoots() will not consider the project has a classpath
//									this.manager
//											.removePerProjectInfo(javaProject, true /* remove external jar files indexes and timestamps
//											*/);
//									// remove container cache for this project
//									this.manager.containerRemove(javaProject);
//									// close project
//									try {
//										javaProject.close();
//									} catch (JavaModelException e) {
//										// java project doesn't exist: ignore
//									}
//									removeFromParentInfo(javaProject);
//								}
//								this.state.rootsAreStale = true;
//							} else {
//								// in case the project was removed then added then changed (see bug 19799)
//								if (isJavaProject) { // need nature check - 18698
//									addToParentInfo(javaProject);
//									children = delta.getAffectedChildren();
//								}
//							}
//						} else {
//							// workaround for bug 15168 circular errors not reported
//							// in case the project was removed then added then changed
//							if (JavaProject.hasJavaNature(project)) { // need nature check - 18698
//								addToParentInfo(javaProject);
//								children = delta.getAffectedChildren();
//							}
//						}
//						break;
//
//					case IResourceDelta.REMOVED:
//						this.manager.forceBatchInitializations(false/*not initAfterLoad*/);
//
//						// remove classpath cache so that initializeRoots() will not consider the project has a classpath
//						this.manager.removePerProjectInfo(javaProject, true /* remove external jar files indexes and timestamps*/);
//						// remove container cache for this project
//						this.manager.containerRemove(javaProject);
//
//						this.state.rootsAreStale = true;
//						break;
//				}
//
//				break;
//			case IResource.FOLDER:
//				if (delta.getKind() == IResourceDelta.CHANGED) { // look for .jar file change to update classpath
//					children = delta.getAffectedChildren();
//				}
//				break;
//			case IResource.FILE :
//				IFile file = (IFile) resource;
//				int kind = delta.getKind();
//				RootInfo rootInfo;
//				if (file.getName().equals(JavaProject.CLASSPATH_FILENAME)) {
//					/* classpath file change */
//					this.manager.forceBatchInitializations(false/*not initAfterLoad*/);
//					switch (kind) {
//						case IResourceDelta.CHANGED :
//							int flags = delta.getFlags();
//							if ((flags & IResourceDelta.CONTENT) == 0  // only consider content change
//								&& (flags & IResourceDelta.ENCODING) == 0 // and encoding change
//								&& (flags & IResourceDelta.MOVED_FROM) == 0) {// and also move and overide scenario (see http://dev
// .eclipse.org/bugs/show_bug.cgi?id=21420)
//								break;
//							}
//						//$FALL-THROUGH$
//						case IResourceDelta.ADDED :
//						case IResourceDelta.REMOVED :
//							javaProject = (JavaProject)JavaCore.create(file.getProject());
//
//							// force to (re)read the .classpath file
//							// in case of removal (IResourceDelta.REMOVED) this will reset the classpath to its default and create the
// right delta
//							// (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=211290)
//							readRawClasspath(javaProject);
//							break;
//					}
//					this.state.rootsAreStale = true;
//				} else if ((rootInfo = rootInfo(file.getFullPath(), kind)) != null && rootInfo.entryKind == IClasspathEntry.CPE_LIBRARY) {
//					javaProject = (JavaProject)JavaCore.create(file.getProject());
//					javaProject.resetResolvedClasspath();
//					this.state.rootsAreStale = true;
//				}
//				break;
//
//		}
//		if (children != null) {
//			for (int i = 0; i < children.length; i++) {
//				checkProjectsAndClasspathChanges(children[i]);
//			}
//		}
    }

    private void checkExternalFolderChange(IProject project, JavaProject javaProject) {
//		ClasspathChange change = this.state.getClasspathChange(project);
//		this.state.addExternalFolderChange(javaProject, change == null ? null : change.oldResolvedClasspath);
    }

    private void checkProjectReferenceChange(IProject project, JavaProject javaProject) {
//		ClasspathChange change = this.state.getClasspathChange(project);
//		this.state.addProjectReferenceChange(javaProject, change == null ? null : change.oldResolvedClasspath);
    }

    private void readRawClasspath(JavaProject javaProject) {
//		// force to (re)read the .classpath file
//		try {
//			PerProjectInfo perProjectInfo = javaProject.getPerProjectInfo();
//			if (!perProjectInfo.writtingRawClasspath) // to avoid deadlock, see https://bugs.eclipse.org/bugs/show_bug.cgi?id=221680
//				perProjectInfo.readAndCacheClasspath(javaProject);
//		} catch (JavaModelException e) {
//			if (VERBOSE) {
//				e.printStackTrace();
//			}
//		}
    }

    private void checkSourceAttachmentChange(IResourceDelta delta, IResource res) {
        IPath rootPath = (IPath)this.state.sourceAttachments.get(externalPath(res));
        if (rootPath != null) {
            RootInfo rootInfo = rootInfo(rootPath, delta.getKind());
            if (rootInfo != null) {
                IJavaProject projectOfRoot = rootInfo.project;
                IPackageFragmentRoot root = null;
                try {
                    // close the root so that source attachment cache is flushed
                    root = projectOfRoot.findPackageFragmentRoot(rootPath);
                    if (root != null) {
                        root.close();
                    }
                } catch (JavaModelException e) {
                    // root doesn't exist: ignore
                }
                if (root == null) return;
                switch (delta.getKind()) {
                    case IResourceDelta.ADDED:
                        currentDelta().sourceAttached(root);
                        break;
                    case IResourceDelta.CHANGED:
                        currentDelta().sourceDetached(root);
                        currentDelta().sourceAttached(root);
                        break;
                    case IResourceDelta.REMOVED:
                        currentDelta().sourceDetached(root);
                        break;
                }
            }
        }
    }

    /*
     * Closes the given element, which removes it from the cache of open elements.
     */
    private void close(Openable element) {
        try {
            element.close();
        } catch (JavaModelException e) {
            // do nothing
        }
    }

    /*
     * Generic processing for elements with changed contents:<ul>
     * <li>The element is closed such that any subsequent accesses will re-open
     * the element reflecting its new structure.
     * <li>An entry is made in the delta reporting a content change (K_CHANGE with F_CONTENT flag set).
     * </ul>
     * Delta argument could be null if processing an external JAR change
     */
    private void contentChanged(Openable element) {

        boolean isPrimary = false;
        boolean isPrimaryWorkingCopy = false;
        if (element.getElementType() == IJavaElement.COMPILATION_UNIT) {
            CompilationUnit cu = (CompilationUnit)element;
            isPrimary = cu.isPrimary();
            isPrimaryWorkingCopy = isPrimary && cu.isWorkingCopy();
        }
        if (isPrimaryWorkingCopy) {
            // filter out changes to primary compilation unit in working copy mode
            // just report a change to the resource (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=59500)
            currentDelta().changed(element, IJavaElementDelta.F_PRIMARY_RESOURCE);
        } else {
            close(element);
            int flags = IJavaElementDelta.F_CONTENT;
            if (element instanceof JarPackageFragmentRoot) {
                flags |= IJavaElementDelta.F_ARCHIVE_CONTENT_CHANGED;
                // need also to reset project cache otherwise it will be out-of-date
                // see bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=162621
                this.projectCachesToReset.add(element.getJavaProject());
            }
            if (isPrimary) {
                flags |= IJavaElementDelta.F_PRIMARY_RESOURCE;
            }
            currentDelta().changed(element, flags);
        }
    }

    /*
     * Creates the openables corresponding to this resource.
     * Returns null if none was found.
     */
    private Openable createElement(IResource resource, int elementType, RootInfo rootInfo) {
        if (resource == null) return null;

        IPath path = resource.getFullPath();
        IJavaElement element = null;
        switch (elementType) {

			case IJavaElement.JAVA_PROJECT:

				// note that non-java resources rooted at the project level will also enter this code with
				// an elementType JAVA_PROJECT (see #elementType(...)).
				if (resource instanceof IProject){

					popUntilPrefixOf(path);

					if (this.currentElement != null
						&& this.currentElement.getElementType() == IJavaElement.JAVA_PROJECT
						&& ((IJavaProject)this.currentElement).getProject().equals(resource)) {
						return this.currentElement;
					}
					if  (rootInfo != null && rootInfo.project.getProject().equals(resource)){
						element = rootInfo.project;
						break;
					}
					IProject proj = (IProject)resource;
					if (JavaProject.hasJavaNature(proj)) {
						element = JavaCore.create(proj);
					} else {
						// java project may have been been closed or removed (look for
						// element amongst old java project s list).
						element =  this.state.findJavaProject(proj.getName());
					}
				}
				break;
            case IJavaElement.PACKAGE_FRAGMENT_ROOT:
                element = rootInfo == null ? JavaCore.create(resource)
                                           : rootInfo.getPackageFragmentRoot(resource);
                break;
            case IJavaElement.PACKAGE_FRAGMENT:
                if (rootInfo != null) {
                    if (rootInfo.project.contains(resource)) {
                        PackageFragmentRoot root = (PackageFragmentRoot)rootInfo.getPackageFragmentRoot(null);
                        // create package handle
                        IPath pkgPath = path.removeFirstSegments(root.resource().getFullPath().segmentCount());
                        String[] pkgName = pkgPath.segments();
                        element = root.getPackageFragment(pkgName);
                    }
                } else {
                    // find the element that encloses the resource
                    popUntilPrefixOf(path);

                    if (this.currentElement == null) {
                        element = JavaCore.create(resource);
                    } else {
                        // find the root
                        PackageFragmentRoot root = this.currentElement.getPackageFragmentRoot();
                        if (root == null) {
                            element = JavaCore.create(resource);
                        } else if (((JavaProject)root.getJavaProject()).contains(resource)) {
                            // create package handle
                            IPath pkgPath = path.removeFirstSegments(root.getPath().segmentCount());
                            String[] pkgName = pkgPath.segments();
                            element = root.getPackageFragment(pkgName);
                        }
                    }
                }
                break;
            case IJavaElement.COMPILATION_UNIT:
            case IJavaElement.CLASS_FILE:
                // find the element that encloses the resource
                popUntilPrefixOf(path);

                if (this.currentElement == null) {
                    element = rootInfo == null ? JavaCore.create(resource)
                                               : JavaModelManager.create(resource, rootInfo.project);
                } else {
                    // find the package
                    IPackageFragment pkgFragment = null;
                    switch (this.currentElement.getElementType()) {
                        case IJavaElement.PACKAGE_FRAGMENT_ROOT:
                            PackageFragmentRoot root = (PackageFragmentRoot)this.currentElement;
                            IPath rootPath = root.getPath();
                            IPath pkgPath = path.removeLastSegments(1);
                            String[] pkgName = pkgPath.removeFirstSegments(rootPath.segmentCount()).segments();
                            pkgFragment = root.getPackageFragment(pkgName);
                            break;
                        case IJavaElement.PACKAGE_FRAGMENT:
                            Openable pkg = this.currentElement;
                            if (pkg.getPath().equals(path.removeLastSegments(1))) {
                                pkgFragment = (IPackageFragment)pkg;
                            } // else case of package x which is a prefix of x.y
                            break;
                        case IJavaElement.COMPILATION_UNIT:
                        case IJavaElement.CLASS_FILE:
                            pkgFragment = (IPackageFragment)this.currentElement.getParent();
                            break;
                    }
                    if (pkgFragment == null) {
                        element = rootInfo == null ? JavaCore.create(resource) : JavaModelManager.create(resource, rootInfo.project);
                    } else {
                        if (elementType == IJavaElement.COMPILATION_UNIT) {
                            // create compilation unit handle
                            // fileName validation has been done in elementType(IResourceDelta, int, boolean)
                            String fileName = path.lastSegment();
                            element = pkgFragment.getCompilationUnit(fileName);
                        } else {
                            // create class file handle
                            // fileName validation has been done in elementType(IResourceDelta, int, boolean)
                            String fileName = path.lastSegment();
                            element = pkgFragment.getClassFile(fileName);
                        }
                    }
                }
                break;
        }
        if (element == null) return null;
        this.currentElement = (Openable)element;
        return this.currentElement;
    }

    public void checkExternalArchiveChanges(IJavaElement[] elementsScope, IProgressMonitor monitor) throws JavaModelException {
        checkExternalArchiveChanges(elementsScope, false, monitor);
    }

    /*
     * Check all external archive (referenced by given roots, projects or model) status and issue a corresponding root delta.
     * Also triggers index updates
     */
    private void checkExternalArchiveChanges(IJavaElement[] elementsScope, boolean asynchronous, IProgressMonitor monitor) throws
                                                                                                                           JavaModelException {
//		if (monitor != null && monitor.isCanceled())
//			throw new OperationCanceledException();
//		try {
//			if (monitor != null) monitor.beginTask("", 1); //$NON-NLS-1$
//
//			boolean hasExternalWorkingCopyProject = false;
//			for (int i = 0, length = elementsScope.length; i < length; i++) {
//				IJavaElement element = elementsScope[i];
//				this.state.addForRefresh(elementsScope[i]);
//				if (element.getElementType() == IJavaElement.JAVA_MODEL) {
//					// ensure external working copies' projects' caches are reset
//					HashSet projects = JavaModelManager.getJavaModelManager().getExternalWorkingCopyProjects();
//					if (projects != null) {
//						hasExternalWorkingCopyProject = true;
//						Iterator iterator = projects.iterator();
//						while (iterator.hasNext()) {
//							JavaProject project = (JavaProject) iterator.next();
//							project.resetCaches();
//						}
//					}
//				}
//			}
//			HashSet elementsToRefresh = this.state.removeExternalElementsToRefresh();
//			boolean hasDelta = elementsToRefresh != null && createExternalArchiveDelta(elementsToRefresh, monitor);
//			if (hasDelta){
//				IJavaElementDelta[] projectDeltas = this.currentDelta.getAffectedChildren();
//				final int length = projectDeltas.length;
//				final IProject[] projectsToTouch = new IProject[length];
//				for (int i = 0; i < length; i++) {
//					IJavaElementDelta delta = projectDeltas[i];
//					JavaProject javaProject = (JavaProject)delta.getElement();
//					projectsToTouch[i] = javaProject.getProject();
//				}
//				if (projectsToTouch.length > 0) {
//					if (asynchronous){
//						WorkspaceJob touchJob = new WorkspaceJob(Messages.updating_external_archives_jobName) {
//
//							public IStatus runInWorkspace(IProgressMonitor progressMonitor) throws CoreException {
//								try {
//									if (progressMonitor != null)
//										progressMonitor.beginTask("", projectsToTouch.length); //$NON-NLS-1$
//									touchProjects(projectsToTouch, progressMonitor);
//								}
//								finally {
//									if (progressMonitor != null)
//										progressMonitor.done();
//								}
//								return Status.OK_STATUS;
//							}
//
//							public boolean belongsTo(Object family) {
//								return ResourcesPlugin.FAMILY_MANUAL_REFRESH == family;
//							}
//						};
//						touchJob.schedule();
//					}
//					else {
//						// touch the projects to force them to be recompiled while taking the workspace lock
//						//	 so that there is no concurrency with the Java builder
//						// see https://bugs.eclipse.org/bugs/show_bug.cgi?id=96575
//						IWorkspaceRunnable runnable = new IWorkspaceRunnable() {
//							public void run(IProgressMonitor progressMonitor) throws CoreException {
//								for (int i = 0; i < projectsToTouch.length; i++) {
//									IProject project = projectsToTouch[i];
//
//									// touch to force a build of this project
//									if (JavaBuilder.DEBUG)
//										System.out.println("Touching project " + project.getName() + " due to external jar file change");
// $NON-NLS-1$ //$NON-NLS-2$
//									project.touch(progressMonitor);
//								}
//							}
//						};
//						try {
//							ResourcesPlugin.getWorkspace().run(runnable, monitor);
//						} catch (CoreException e) {
//							throw new JavaModelException(e);
//						}
//					}
//				}
//
//				if (this.currentDelta != null) { // if delta has not been fired while creating markers
//					fire(this.currentDelta, DEFAULT_CHANGE_EVENT);
//				}
//			} else if (hasExternalWorkingCopyProject) {
//				// flush jar type cache
//				JavaModelManager.getJavaModelManager().resetJarTypeCache();
//			}
//		} finally {
//			this.currentDelta = null;
//			if (monitor != null) monitor.done();
//		}
        throw new UnsupportedOperationException();
    }

    protected void touchProjects(final IProject[] projectsToTouch, IProgressMonitor progressMonitor)
            throws CoreException {
        for (int i = 0; i < projectsToTouch.length; i++) {
            IProgressMonitor monitor = progressMonitor == null ? null : new SubProgressMonitor(progressMonitor, 1);
            IProject project = projectsToTouch[i];
            // touch to force a build of this project
            if (JavaBuilder.DEBUG)
                System.out
                        .println("Touching project " + project.getName() + " due to external jar file change"); //$NON-NLS-1$ //$NON-NLS-2$
            project.touch(monitor);
        }
    }

    /*
     * Check if external archives have changed for the given elements and create the corresponding deltas.
     * Returns whether at least one delta was created.
     */
    private boolean createExternalArchiveDelta(HashSet refreshedElements, IProgressMonitor monitor) {

        HashMap externalArchivesStatus = new HashMap();
        boolean hasDelta = false;

        // find JARs to refresh
        HashSet archivePathsToRefresh = new HashSet();
        Iterator iterator = refreshedElements.iterator();
        while (iterator.hasNext()) {
            IJavaElement element = (IJavaElement)iterator.next();
            switch (element.getElementType()) {
                case IJavaElement.PACKAGE_FRAGMENT_ROOT:
                    archivePathsToRefresh.add(element.getPath());
                    break;
                case IJavaElement.JAVA_PROJECT:
                    JavaProject javaProject = (JavaProject)element;
                    if (!JavaProject.hasJavaNature(javaProject.getProject())) {
                        // project is not accessible or has lost its Java nature
                        break;
                    }
                    IClasspathEntry[] classpath;
                    try {
                        classpath = javaProject.getResolvedClasspath();
                        for (int j = 0, cpLength = classpath.length; j < cpLength; j++) {
                            if (classpath[j].getEntryKind() == IClasspathEntry.CPE_LIBRARY) {
                                archivePathsToRefresh.add(classpath[j].getPath());
                            }
                        }
                    } catch (JavaModelException e) {
                        // project doesn't exist -> ignore
                    }
                    break;
                case IJavaElement.JAVA_MODEL:
//					Iterator projectNames = this.state.getOldJavaProjecNames().iterator();
//					while (projectNames.hasNext()) {
//						String projectName = (String) projectNames.next();
//						IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(projectName);
//						if (!JavaProject.hasJavaNature(project)) {
//							// project is not accessible or has lost its Java nature
//							continue;
//						}
//						javaProject = (JavaProject) JavaCore.create(project);
//						try {
//							classpath = javaProject.getResolvedClasspath();
//							for (int k = 0, cpLength = classpath.length; k < cpLength; k++){
//								if (classpath[k].getEntryKind() == IClasspathEntry.CPE_LIBRARY){
//									archivePathsToRefresh.add(classpath[k].getPath());
//								}
//							}
//						} catch (JavaModelException e2) {
//							// project doesn't exist -> ignore
//							continue;
//						}
//					}
                    throw new UnsupportedOperationException();
//					break;
            }
        }

//		// perform refresh
//		Iterator projectNames = this.state.getOldJavaProjecNames().iterator();
//		IWorkspaceRoot wksRoot = ResourcesPlugin.getWorkspace().getRoot();
//		while (projectNames.hasNext()) {
//
//			if (monitor != null && monitor.isCanceled()) break;
//
//			String projectName = (String) projectNames.next();
//			IProject project = wksRoot.getProject(projectName);
//			if (!JavaProject.hasJavaNature(project)) {
//				// project is not accessible or has lost its Java nature
//				continue;
//			}
//			JavaProject javaProject = (JavaProject) JavaCore.create(project);
//			IClasspathEntry[] entries;
//			try {
//				entries = javaProject.getResolvedClasspath();
//			} catch (JavaModelException e1) {
//				// project does not exist -> ignore
//				continue;
//			}
//			boolean deltaContainsModifiedJar = false;
//			for (int j = 0; j < entries.length; j++){
//				if (entries[j].getEntryKind() == IClasspathEntry.CPE_LIBRARY) {
//					IPath entryPath = entries[j].getPath();
//
//					if (!archivePathsToRefresh.contains(entryPath)) continue; // not supposed to be refreshed
//
//					String status = (String)externalArchivesStatus.get(entryPath);
//					if (status == null){
//
//						// Clear the external file state for this path, since this method is responsible for updating it.
//						this.manager.clearExternalFileState(entryPath);
//
//						// compute shared status
//						Object targetLibrary = JavaModel.getTarget(entryPath, true);
//
//						if (targetLibrary == null){ // missing JAR
//							if (this.state.getExternalLibTimeStamps().remove(entryPath) != null /* file was known*/
//									&& this.state.roots.get(entryPath) != null /* and it was on the classpath*/) {
//								externalArchivesStatus.put(entryPath, EXTERNAL_JAR_REMOVED);
//								// the jar was physically removed: remove the index
//								this.manager.indexManager.removeIndex(entryPath);
//							}
//
//						} else if (targetLibrary instanceof File){ // external JAR
//
//							File externalFile = (File)targetLibrary;
//
//							// check timestamp to figure if JAR has changed in some way
//							Long oldTimestamp =(Long) this.state.getExternalLibTimeStamps().get(entryPath);
//							long newTimeStamp = getTimeStamp(externalFile);
//							if (oldTimestamp != null){
//
//								if (newTimeStamp == 0){ // file doesn't exist
//									externalArchivesStatus.put(entryPath, EXTERNAL_JAR_REMOVED);
//									this.state.getExternalLibTimeStamps().remove(entryPath);
//									// remove the index
//									this.manager.indexManager.removeIndex(entryPath);
//
//								} else if (oldTimestamp.longValue() != newTimeStamp){
//									externalArchivesStatus.put(entryPath, EXTERNAL_JAR_CHANGED);
//									this.state.getExternalLibTimeStamps().put(entryPath, new Long(newTimeStamp));
//									// first remove the index so that it is forced to be re-indexed
//									this.manager.indexManager.removeIndex(entryPath);
//									// then index the jar
//									this.manager.indexManager.indexLibrary(entryPath, project.getProject(), ((ClasspathEntry)entries[j])
// .getLibraryIndexLocation(), true);
//								} else {
//									URL indexLocation = ((ClasspathEntry)entries[j]).getLibraryIndexLocation();
//									if (indexLocation != null) { // force reindexing, this could be faster rather than maintaining the list
//										this.manager.indexManager.indexLibrary(entryPath, project.getProject(), indexLocation);
//									}
//									externalArchivesStatus.put(entryPath, EXTERNAL_JAR_UNCHANGED);
//								}
//							} else {
//								if (newTimeStamp == 0){ // jar still doesn't exist
//									externalArchivesStatus.put(entryPath, EXTERNAL_JAR_UNCHANGED);
//								} else {
//									externalArchivesStatus.put(entryPath, EXTERNAL_JAR_ADDED);
//									this.state.getExternalLibTimeStamps().put(entryPath, new Long(newTimeStamp));
//									// index the new jar
//									this.manager.indexManager.removeIndex(entryPath);
//									this.manager.indexManager.indexLibrary(entryPath, project.getProject(), ((ClasspathEntry)entries[j])
// .getLibraryIndexLocation());
//								}
//							}
//						} else { // internal JAR
//							externalArchivesStatus.put(entryPath, INTERNAL_JAR_IGNORE);
//						}
//					}
//					// according to computed status, generate a delta
//					status = (String)externalArchivesStatus.get(entryPath);
//					if (status != null){
//						if (status == EXTERNAL_JAR_ADDED){
//							PackageFragmentRoot root = (PackageFragmentRoot) javaProject.getPackageFragmentRoot(entryPath.toString());
//							if (VERBOSE){
//								System.out.println("- External JAR ADDED, affecting root: "+root.getElementName()); //$NON-NLS-1$
//							}
//							elementAdded(root, null, null);
//							deltaContainsModifiedJar = true;
//							this.state.addClasspathValidation(javaProject); // see https://bugs.eclipse.org/bugs/show_bug.cgi?id=185733
//							hasDelta = true;
//						} else if (status == EXTERNAL_JAR_CHANGED) {
//							PackageFragmentRoot root = (PackageFragmentRoot) javaProject.getPackageFragmentRoot(entryPath.toString());
//							if (VERBOSE){
//								System.out.println("- External JAR CHANGED, affecting root: "+root.getElementName()); //$NON-NLS-1$
//							}
//							contentChanged(root);
//							deltaContainsModifiedJar = true;
//							hasDelta = true;
//						} else if (status == EXTERNAL_JAR_REMOVED) {
//							PackageFragmentRoot root = (PackageFragmentRoot) javaProject.getPackageFragmentRoot(entryPath.toString());
//							if (VERBOSE){
//								System.out.println("- External JAR REMOVED, affecting root: "+root.getElementName()); //$NON-NLS-1$
//							}
//							elementRemoved(root, null, null);
//							deltaContainsModifiedJar = true;
//							this.state.addClasspathValidation(javaProject); // see https://bugs.eclipse.org/bugs/show_bug.cgi?id=185733
//							hasDelta = true;
//						}
//					}
//				}
//			}
//
//			if (deltaContainsModifiedJar) {
//				javaProject.resetResolvedClasspath();
//			}
//		}
//
//		if (hasDelta){
//			// flush jar type cache
//			JavaModelManager.getJavaModelManager().resetJarTypeCache();
//		}
        return hasDelta;
    }

    private JavaElementDelta currentDelta() {
        if (this.currentDelta == null) {
            this.currentDelta = new JavaElementDelta(this.manager.getJavaModel());
        }
        return this.currentDelta;
    }

    /*
     * Note that the project is about to be deleted.
     */
    private void deleting(IProject project) {

//		try {
//			// discard indexing jobs that belong to this project so that the project can be
//			// deleted without interferences from the index manager
//			this.manager.indexManager.discardJobs(project.getName());
//
//			JavaProject javaProject = (JavaProject)JavaCore.create(project);
//
//			// remember roots of this project
//			if (this.oldRoots == null) {
//				this.oldRoots = new HashMap();
//			}
//			if (javaProject.isOpen()) {
//				this.oldRoots.put(javaProject, javaProject.getPackageFragmentRoots());
//			} else {
//				// compute roots without opening project
//				this.oldRoots.put(
//					javaProject,
//					javaProject.computePackageFragmentRoots(
//						javaProject.getResolvedClasspath(),
//						false,
//						null /*no reverse map*/));
//			}
//
//			javaProject.close();
//
//			// workaround for bug 15168 circular errors not reported
//			this.state.getOldJavaProjecNames(); // foce list to be computed
//
//			removeFromParentInfo(javaProject);
//
//			// remove preferences from per project info
//			this.manager.resetProjectPreferences(javaProject);
//		} catch (JavaModelException e) {
//			// java project doesn't exist: ignore
//		}
        throw new UnsupportedOperationException();
    }

    /*
     * Processing for an element that has been added:<ul>
     * <li>If the element is a project, do nothing, and do not process
     * children, as when a project is created it does not yet have any
     * natures - specifically a java nature.
     * <li>If the elemet is not a project, process it as added (see
     * <code>basicElementAdded</code>.
     * </ul>
     * Delta argument could be null if processing an external JAR change
     */
    private void elementAdded(Openable element, IResourceDelta delta, RootInfo rootInfo) {
        int elementType = element.getElementType();

        if (elementType == IJavaElement.JAVA_PROJECT) {
            // project add is handled by JavaProject.configure() because
            // when a project is created, it does not yet have a Java nature
            IProject project;
            if (delta != null && JavaProject.hasJavaNature(project = (IProject)delta.getResource())) {
                addToParentInfo(element);
                this.manager.getPerProjectInfo(project, true /*create info if needed*/).rememberExternalLibTimestamps();
                if ((delta.getFlags() & IResourceDelta.MOVED_FROM) != 0) {
                    Openable movedFromElement = (Openable)element.getJavaModel().getJavaProject(delta.getMovedFromPath().lastSegment());
                    currentDelta().movedTo(element, movedFromElement);
                } else {
                    // Force the project to be closed as it might have been opened
                    // before the resource modification came in and it might have a new child
                    // For example, in an IWorkspaceRunnable:
                    // 1. create a Java project P (where P=src)
                    // 2. open project P
                    // 3. add folder f in P's pkg fragment root
                    // When the resource delta comes in, only the addition of P is notified,
                    // but the pkg fragment root of project P is already opened, thus its children are not recomputed
                    // and it appears to contain only the default package.
                    close(element);

                    currentDelta().added(element);
                }
                this.state.updateRoots(element.getPath(), delta, this);

                // remember that the project's cache must be reset
                this.projectCachesToReset.add(element);
            }
        } else {
            if (delta == null || (delta.getFlags() & IResourceDelta.MOVED_FROM) == 0) {
                // regular element addition
                if (isPrimaryWorkingCopy(element, elementType)) {
                    // filter out changes to primary compilation unit in working copy mode
                    // just report a change to the resource (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=59500)
                    currentDelta().changed(element, IJavaElementDelta.F_PRIMARY_RESOURCE);
                } else {
                    addToParentInfo(element);

                    // Force the element to be closed as it might have been opened
                    // before the resource modification came in and it might have a new child
                    // For example, in an IWorkspaceRunnable:
                    // 1. create a package fragment p using a java model operation
                    // 2. open package p
                    // 3. add file X.java in folder p
                    // When the resource delta comes in, only the addition of p is notified,
                    // but the package p is already opened, thus its children are not recomputed
                    // and it appears empty.
                    close(element);

                    currentDelta().added(element);
                }
            } else {
                // element is moved
                addToParentInfo(element);
                close(element);

                IPath movedFromPath = delta.getMovedFromPath();
                IResource res = delta.getResource();
                IResource movedFromRes;
                if (res instanceof IFile) {
                    movedFromRes = res.getWorkspace().getRoot().getFile(movedFromPath);
                } else {
                    movedFromRes = res.getWorkspace().getRoot().getFolder(movedFromPath);
                }

                // find the element type of the moved from element
                IPath rootPath = externalPath(movedFromRes);
                RootInfo movedFromInfo = enclosingRootInfo(rootPath, IResourceDelta.REMOVED);
                int movedFromType =
                        elementType(
                                movedFromRes,
                                IResourceDelta.REMOVED,
                                element.getParent().getElementType(),
                                movedFromInfo);

                // reset current element as it might be inside a nested root (popUntilPrefixOf() may use the outer root)
                this.currentElement = null;

                // create the moved from element
                Openable movedFromElement =
                        elementType != IJavaElement.JAVA_PROJECT && movedFromType == IJavaElement.JAVA_PROJECT ?
                        null : // outside classpath
                        createElement(movedFromRes, movedFromType, movedFromInfo);
                if (movedFromElement == null) {
                    // moved from outside classpath
                    currentDelta().added(element);
                } else {
                    currentDelta().movedTo(element, movedFromElement);
                }
            }

            switch (elementType) {
                case IJavaElement.PACKAGE_FRAGMENT_ROOT:
                    // when a root is added, and is on the classpath, the project must be updated
                    JavaProject project = (JavaProject)element.getJavaProject();

                    // remember that the project's cache must be reset
                    this.projectCachesToReset.add(project);

                    break;
                case IJavaElement.PACKAGE_FRAGMENT:
                    // reset project's package fragment cache
                    project = (JavaProject)element.getJavaProject();
                    this.projectCachesToReset.add(project);

                    break;
            }
        }
    }

    /*
     * Generic processing for a removed element:<ul>
     * <li>Close the element, removing its structure from the cache
     * <li>Remove the element from its parent's cache of children
     * <li>Add a REMOVED entry in the delta
     * </ul>
     * Delta argument could be null if processing an external JAR change
     */
    private void elementRemoved(Openable element, IResourceDelta delta, RootInfo rootInfo) {

        int elementType = element.getElementType();
        if (delta == null || (delta.getFlags() & IResourceDelta.MOVED_TO) == 0) {
            // regular element removal
            if (isPrimaryWorkingCopy(element, elementType)) {
                // filter out changes to primary compilation unit in working copy mode
                // just report a change to the resource (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=59500)
                currentDelta().changed(element, IJavaElementDelta.F_PRIMARY_RESOURCE);
            } else {
                close(element);
                removeFromParentInfo(element);
                currentDelta().removed(element);
            }
        } else {
            // element is moved
            close(element);
            removeFromParentInfo(element);
            IPath movedToPath = delta.getMovedToPath();
            IResource res = delta.getResource();
            IResource movedToRes;
//            movedToRes = movedToPath.toFile();
			switch (res.getType()) {
				case IResource.PROJECT:
					movedToRes = res.getWorkspace().getRoot().getProject(movedToPath.lastSegment());
					break;
				case IResource.FOLDER:
					movedToRes = res.getWorkspace().getRoot().getFolder(movedToPath);
					break;
				case IResource.FILE:
					movedToRes = res.getWorkspace().getRoot().getFile(movedToPath);
					break;
				default:
					return;
			}

            // find the element type of the moved from element
            IPath rootPath = externalPath(movedToRes);
            RootInfo movedToInfo = enclosingRootInfo(rootPath, IResourceDelta.ADDED);
            int movedToType =
                    elementType(
                            movedToRes,
                            IResourceDelta.ADDED,
                            element.getParent().getElementType(),
                            movedToInfo);

            // reset current element as it might be inside a nested root (popUntilPrefixOf() may use the outer root)
            this.currentElement = null;

            // create the moved To element
            Openable movedToElement =
                    elementType != IJavaElement.JAVA_PROJECT && movedToType == IJavaElement.JAVA_PROJECT ?
                    null : // outside classpath
                    createElement(movedToRes, movedToType, movedToInfo);
            if (movedToElement == null) {
                // moved outside classpath
                currentDelta().removed(element);
            } else {
                currentDelta().movedFrom(element, movedToElement);
            }
        }

        switch (elementType) {
            case IJavaElement.JAVA_MODEL:
                this.manager.indexManager.reset();
                break;
            case IJavaElement.JAVA_PROJECT:
                this.state.updateRoots(element.getPath(), delta, this);
                //TODO: this is quick fix for https://jira.codenvycorp.com/browse/IDEX-4221
                //we do it because we need clear cache for deleted project but we don't know how do it right way
                //so we clean all cache totally 
                this.state.roots.clear();
                // remember that the project's cache must be reset
                this.projectCachesToReset.add(element);

                break;
            case IJavaElement.PACKAGE_FRAGMENT_ROOT:
                JavaProject project = (JavaProject)element.getJavaProject();

                // remember that the project's cache must be reset
                this.projectCachesToReset.add(project);

                break;
            case IJavaElement.PACKAGE_FRAGMENT:
                // reset package fragment cache
                project = (JavaProject)element.getJavaProject();
                this.projectCachesToReset.add(project);

                break;
        }
    }

    /*
     * Returns the type of the java element the given delta matches to.
     * Returns NON_JAVA_RESOURCE if unknown (e.g. a non-java resource or excluded .java file)
     */
    private int elementType(IResource res, int kind, int parentType, RootInfo rootInfo) {
        switch (parentType) {
            case IJavaElement.JAVA_MODEL:
                // case of a movedTo or movedFrom project (other cases are handled in processResourceDelta(...)
                return IJavaElement.JAVA_PROJECT;

            case NON_JAVA_RESOURCE:
            case IJavaElement.JAVA_PROJECT:
                if (rootInfo == null) {
                    rootInfo = enclosingRootInfo(res.getFullPath(), kind);
                }
                if (rootInfo != null && rootInfo.isRootOfProject(res.getFullPath())) {
                    return IJavaElement.PACKAGE_FRAGMENT_ROOT;
                }
                // not yet in a package fragment root or root of another project
                // or package fragment to be included (see below)
                // $FALL-THROUGH$

            case IJavaElement.PACKAGE_FRAGMENT_ROOT:
            case IJavaElement.PACKAGE_FRAGMENT:
                if (rootInfo == null) {
                    IPath rootPath = externalPath(res);
                    rootInfo = enclosingRootInfo(rootPath, kind);
                }
                if (rootInfo == null) {
                    return NON_JAVA_RESOURCE;
                }
                if (Util.isExcluded(res, rootInfo.inclusionPatterns, rootInfo.exclusionPatterns)) {
                    return NON_JAVA_RESOURCE;
                }
                if (res.getType() == IResource.FOLDER) {
                    if (parentType == NON_JAVA_RESOURCE && !Util
                            .isExcluded(res.getParent(), rootInfo.inclusionPatterns, rootInfo.exclusionPatterns)) {
                        // parent is a non-Java resource because it doesn't have a valid package name (see https://bugs.eclipse
                        // .org/bugs/show_bug.cgi?id=130982)
                        return NON_JAVA_RESOURCE;
                    }
                    String sourceLevel = rootInfo.project == null ? null : rootInfo.project.getOption(JavaCore.COMPILER_SOURCE, true);
                    String complianceLevel =
                            rootInfo.project == null ? null : rootInfo.project.getOption(JavaCore.COMPILER_COMPLIANCE, true);
                    if (Util.isValidFolderNameForPackage(res.getName(), sourceLevel, complianceLevel)) {
                        return IJavaElement.PACKAGE_FRAGMENT;
                    }
                    return NON_JAVA_RESOURCE;
                }
                String fileName = res.getName();
                String sourceLevel = rootInfo.project == null ? null : rootInfo.project.getOption(JavaCore.COMPILER_SOURCE, true);
                String complianceLevel = rootInfo.project == null ? null : rootInfo.project.getOption(JavaCore.COMPILER_COMPLIANCE, true);
                if (Util.isValidCompilationUnitName(fileName, sourceLevel, complianceLevel)) {
                    return IJavaElement.COMPILATION_UNIT;
                } else if (Util.isValidClassFileName(fileName, sourceLevel, complianceLevel)) {
                    return IJavaElement.CLASS_FILE;
                } else {
                    IPath rootPath = externalPath(res);
                    if ((rootInfo = rootInfo(rootPath, kind)) != null
                        && rootInfo.project.getProject().getFullPath().isPrefixOf(
                            rootPath) /*ensure root is a root of its project (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=185310)
                            */) {
                        // case of proj=src=bin and resource is a jar file on the classpath
                        return IJavaElement.PACKAGE_FRAGMENT_ROOT;
                    } else {
                        return NON_JAVA_RESOURCE;
                    }
                }

            default:
                return NON_JAVA_RESOURCE;
        }
    }

    /*
     * Flushes all deltas without firing them.
     */
    public void flush() {
        this.javaModelDeltas = new ArrayList();
    }

    private SourceElementParser getSourceElementParser(Openable element) {
        if (this.sourceElementParserCache == null)
            this.sourceElementParserCache =
                    this.manager.indexManager.getSourceElementParser(element.getJavaProject(), null/*requestor will be set by indexer*/);
        return this.sourceElementParserCache;
    }

    /*
     * Finds the root info this path is included in.
     * Returns null if not found.
     */
    private RootInfo enclosingRootInfo(IPath path, int kind) {
        while (path != null && path.segmentCount() > 0) {
            RootInfo rootInfo = rootInfo(path, kind);
            if (rootInfo != null) return rootInfo;
            path = path.removeLastSegments(1);
        }
        return null;
    }

    private IPath externalPath(IResource res) {
        IPath resourcePath = res.getFullPath();
//		if (ExternalFoldersManager.isInternalPathForExternalFolder(resourcePath))
//			return res.getLocation();
        return resourcePath;
    }

    /*
     * Fire Java Model delta, flushing them after the fact after post_change notification.
     * If the firing mode has been turned off, this has no effect.
     */
    public void fire(IJavaElementDelta customDelta, int eventType) {
        if (!this.isFiring) return;

        if (DEBUG) {
            System.out.println(
                    "-----------------------------------------------------------------------------------------------------------------------");//$NON-NLS-1$
        }

        IJavaElementDelta deltaToNotify;
        if (customDelta == null) {
            deltaToNotify = mergeDeltas(this.javaModelDeltas);
        } else {
            deltaToNotify = customDelta;
        }

        // Refresh internal scopes
        if (deltaToNotify != null) {
            Iterator scopes = this.manager.searchScopes.keySet().iterator();
            while (scopes.hasNext()) {
                AbstractSearchScope scope = (AbstractSearchScope)scopes.next();
                scope.processDelta(deltaToNotify, eventType);
            }
            JavaWorkspaceScope workspaceScope = this.manager.workspaceScope;
            if (workspaceScope != null)
                workspaceScope.processDelta(deltaToNotify, eventType);
        }

        // Notification

        // Important: if any listener reacts to notification by updating the listeners list or mask, these lists will
        // be duplicated, so it is necessary to remember original lists in a variable (since field values may change under us)
        IElementChangedListener[] listeners;
        int[] listenerMask;
        int listenerCount;
        synchronized (this.state) {
            listeners = this.state.elementChangedListeners;
            listenerMask = this.state.elementChangedListenerMasks;
            listenerCount = this.state.elementChangedListenerCount;
        }

        switch (eventType) {
            case DEFAULT_CHANGE_EVENT:
            case ElementChangedEvent.POST_CHANGE:
                firePostChangeDelta(deltaToNotify, listeners, listenerMask, listenerCount);
                fireReconcileDelta(listeners, listenerMask, listenerCount);
                break;
        }
    }

    private void firePostChangeDelta(
            IJavaElementDelta deltaToNotify,
            IElementChangedListener[] listeners,
            int[] listenerMask,
            int listenerCount) {

        // post change deltas
        if (DEBUG) {
            System.out.println("FIRING POST_CHANGE Delta [" + Thread.currentThread() + "]:"); //$NON-NLS-1$//$NON-NLS-2$
            System.out.println(deltaToNotify == null ? "<NONE>" : deltaToNotify.toString()); //$NON-NLS-1$
        }
        if (deltaToNotify != null) {
            // flush now so as to keep listener reactions to post their own deltas for subsequent iteration
            flush();

//			// mark the operation stack has not modifying resources since resource deltas are being fired
//			JavaModelOperation.setAttribute(JavaModelOperation.HAS_MODIFIED_RESOURCE_ATTR, null);

            notifyListeners(deltaToNotify, ElementChangedEvent.POST_CHANGE, listeners, listenerMask, listenerCount);
        }
    }

    private void fireReconcileDelta(
            IElementChangedListener[] listeners,
            int[] listenerMask,
            int listenerCount) {


        IJavaElementDelta deltaToNotify = mergeDeltas(this.reconcileDeltas.values());
        if (DEBUG) {
            System.out.println("FIRING POST_RECONCILE Delta [" + Thread.currentThread() + "]:"); //$NON-NLS-1$//$NON-NLS-2$
            System.out.println(deltaToNotify == null ? "<NONE>" : deltaToNotify.toString()); //$NON-NLS-1$
        }
        if (deltaToNotify != null) {
            // flush now so as to keep listener reactions to post their own deltas for subsequent iteration
            this.reconcileDeltas = new HashMap();

            notifyListeners(deltaToNotify, ElementChangedEvent.POST_RECONCILE, listeners, listenerMask, listenerCount);
        }
    }

    /*
     * Returns whether a given delta contains some information relevant to the JavaModel,
     * in particular it will not consider SYNC or MARKER only deltas.
     */
    private boolean isAffectedBy(IResourceDelta rootDelta) {
        //if (rootDelta == null) System.out.println("NULL DELTA");
        //long start = System.currentTimeMillis();
        if (rootDelta != null) {
            // use local exception to quickly escape from delta traversal
            class FoundRelevantDeltaException extends RuntimeException {
                private static final long serialVersionUID = 7137113252936111022L; // backward compatible
                // only the class name is used (to differenciate from other RuntimeExceptions)
            }
            try {
                rootDelta.accept(new IResourceDeltaVisitor() {
                                     @Override
                                     public boolean visit(org.eclipse.core.resources.IResourceDelta delta) throws CoreException {
                                         switch (delta.getKind()) {
                                             case IResourceDelta.ADDED:
                                             case IResourceDelta.REMOVED:
                                                 throw new FoundRelevantDeltaException();
                                             case IResourceDelta.CHANGED:
                                                 // if any flag is set but SYNC or MARKER, this delta should be considered
//												 if (delta.getAffectedChildren().length == 0 // only check leaf delta nodes
//													 && (delta.getFlags() & ~(IResourceDelta.SYNC | IResourceDelta.MARKERS)) != 0) {
                                                 throw new FoundRelevantDeltaException();
//												 }
                                         }
                                         return true;
                                     }
                                 },
                                 IContainer.INCLUDE_HIDDEN);
            } catch (FoundRelevantDeltaException e) {
                //System.out.println("RELEVANT DELTA detected in: "+ (System.currentTimeMillis() - start));
                return true;
            } catch (CoreException e) { // ignore delta if not able to traverse
            }
        }
        //System.out.println("IGNORE SYNC DELTA took: "+ (System.currentTimeMillis() - start));
        return false;
    }

    /*
     * Returns whether the given element is a primary compilation unit in working copy mode.
     */
    private boolean isPrimaryWorkingCopy(IJavaElement element, int elementType) {
        if (elementType == IJavaElement.COMPILATION_UNIT) {
            CompilationUnit cu = (CompilationUnit)element;
            return cu.isPrimary() && cu.isWorkingCopy();
        }
        return false;
    }

    /*
     * Returns whether the given resource is in one of the given output folders and if
     * it is filtered out from this output folder.
     */
    private boolean isResFilteredFromOutput(RootInfo rootInfo, OutputsInfo info, IResource res, int elementType) {
        if (info != null) {
            JavaProject javaProject = null;
            String sourceLevel = null;
            String complianceLevel = null;
            IPath resPath = res.getFullPath();
            for (int i = 0; i < info.outputCount; i++) {
                if (info.paths[i].isPrefixOf(resPath)) {
                    if (info.traverseModes[i] != IGNORE) {
                        // case of bin=src
                        if (info.traverseModes[i] == SOURCE && elementType == IJavaElement.CLASS_FILE) {
                            return true;
                        }
                        // case of .class file under project and no source folder
                        // proj=bin
                        if (elementType == IJavaElement.JAVA_PROJECT && res instanceof IFile) {
//							if (sourceLevel == null) {
//								// Get java project to use its source and compliance levels
//								javaProject = rootInfo == null ?
//									(JavaProject)createElement(res.getProject(), IJavaElement.JAVA_PROJECT, null) :
//									rootInfo.project;
//								if (javaProject != null) {
//									sourceLevel = javaProject.getOption(JavaCore.COMPILER_SOURCE, true);
//									complianceLevel = javaProject.getOption(JavaCore.COMPILER_COMPLIANCE, true);
//								}
//							}
                            if (Util.isValidClassFileName(res.getName(), sourceLevel, complianceLevel)) {
                                return true;
                            }
                        }
                    } else {
                        return true;
                    }
                }
            }
        }
        return false;
    }

    /*
     * Merges all awaiting deltas.
     */
    private IJavaElementDelta mergeDeltas(Collection deltas) {
        if (deltas.size() == 0) return null;
        if (deltas.size() == 1) return (IJavaElementDelta)deltas.iterator().next();

        if (VERBOSE) {
            System.out.println(
                    "MERGING " + deltas.size() + " DELTAS [" + Thread.currentThread() + "]"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
        }

        Iterator iterator = deltas.iterator();
        JavaElementDelta rootDelta = new JavaElementDelta(this.manager.javaModel);
        boolean insertedTree = false;
        while (iterator.hasNext()) {
            JavaElementDelta delta = (JavaElementDelta)iterator.next();
            if (VERBOSE) {
                System.out.println(delta.toString());
            }
            IJavaElement element = delta.getElement();
            if (this.manager.javaModel.equals(element)) {
                IJavaElementDelta[] children = delta.getAffectedChildren();
                for (int j = 0; j < children.length; j++) {
                    JavaElementDelta projectDelta = (JavaElementDelta)children[j];
                    rootDelta.insertDeltaTree(projectDelta.getElement(), projectDelta);
                    insertedTree = true;
                }
                IResourceDelta[] resourceDeltas = delta.getResourceDeltas();
                if (resourceDeltas != null) {
                    for (int i = 0, length = resourceDeltas.length; i < length; i++) {
                        rootDelta.addResourceDelta(resourceDeltas[i]);
                        insertedTree = true;
                    }
                }
            } else {
                rootDelta.insertDeltaTree(element, delta);
                insertedTree = true;
            }
        }
        if (insertedTree) return rootDelta;
        return null;
    }

    private void notifyListeners(IJavaElementDelta deltaToNotify, int eventType, IElementChangedListener[] listeners, int[] listenerMask,
                                 int listenerCount) {
        final ElementChangedEvent extraEvent = new ElementChangedEvent(deltaToNotify, eventType);
        for (int i = 0; i < listenerCount; i++) {
            if ((listenerMask[i] & eventType) != 0) {
                final IElementChangedListener listener = listeners[i];
                long start = -1;
                if (VERBOSE) {
                    System.out.print("Listener #" + (i + 1) + "=" + listener.toString());//$NON-NLS-1$//$NON-NLS-2$
                    start = System.currentTimeMillis();
                }
                // wrap callbacks with Safe runnable for subsequent listeners to be called when some are causing grief
                SafeRunner.run(new ISafeRunnable() {
                    public void handleException(Throwable exception) {
                        Util.log(exception, "Exception occurred in listener of Java element change notification"); //$NON-NLS-1$
                    }

                    public void run() throws Exception {
                        PerformanceStats stats = null;
                        if (PERF) {
//							stats = PerformanceStats.getStats(JavaModelManager.DELTA_LISTENER_PERF, listener);
//							stats.startRun();
                        }
                        listener.elementChanged(extraEvent);
                        if (PERF) {
                            stats.endRun();
                        }
                    }
                });
                if (VERBOSE) {
                    System.out.println(" -> " + (System.currentTimeMillis() - start) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$
                }
            }
        }
    }

    private void notifyTypeHierarchies(IElementChangedListener[] listeners, int listenerCount) {
        for (int i = 0; i < listenerCount; i++) {
            final IElementChangedListener listener = listeners[i];
            if (!(listener instanceof TypeHierarchy)) continue;

            // wrap callbacks with Safe runnable for subsequent listeners to be called when some are causing grief
            SafeRunner.run(new ISafeRunnable() {
                public void handleException(Throwable exception) {
                    Util.log(exception, "Exception occurred in listener of Java element change notification"); //$NON-NLS-1$
                }

                public void run() throws Exception {
                    TypeHierarchy typeHierarchy = (TypeHierarchy)listener;
                    if (typeHierarchy.hasFineGrainChanges()) {
                        // case of changes in primary working copies
                        typeHierarchy.needsRefresh = true;
                        typeHierarchy.fireChange();
                    }
                }
            });
        }
    }

    /*
     * Generic processing for elements with changed contents:<ul>
     * <li>The element is closed such that any subsequent accesses will re-open
     * the element reflecting its new structure.
     * <li>An entry is made in the delta reporting a content change (K_CHANGE with F_CONTENT flag set).
     * </ul>
     */
    private void nonJavaResourcesChanged(Openable element, IResourceDelta delta) throws JavaModelException {
//		// reset non-java resources if element was open
//		if (element.isOpen()) {
//			JavaElementInfo info = (JavaElementInfo)element.getElementInfo();
//			switch (element.getElementType()) {
//				case IJavaElement.JAVA_MODEL :
//					((JavaModelInfo) info).nonJavaResources = null;
//					if (!ExternalFoldersManager.isInternalPathForExternalFolder(delta.getFullPath()))
//						currentDelta().addResourceDelta(delta);
//					return;
//				case IJavaElement.JAVA_PROJECT :
//					((org.eclipse.jdt.internal.core.JavaProjectElementInfo) info).setNonJavaResources(null);
//
//					// if a package fragment root is the project, clear it too
//					JavaProject project = (JavaProject) element;
//					PackageFragmentRoot projectRoot =
//						(PackageFragmentRoot) project.getPackageFragmentRoot(project.getProject());
//					if (projectRoot.isOpen()) {
//						((org.eclipse.jdt.internal.core.PackageFragmentRootInfo) projectRoot.getElementInfo()).setNonJavaResources(null);
//					}
//					break;
//				case IJavaElement.PACKAGE_FRAGMENT :
//					 ((org.eclipse.jdt.internal.core.PackageFragmentInfo) info).setNonJavaResources(null);
//					break;
//				case IJavaElement.PACKAGE_FRAGMENT_ROOT :
//					 ((org.eclipse.jdt.internal.core.PackageFragmentRootInfo) info).setNonJavaResources(null);
//			}
//		}
//
//		JavaElementDelta current = currentDelta();
//		JavaElementDelta elementDelta = current.find(element);
//		if (elementDelta == null) {
//			// don't use find after creating the delta as it can be null (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=63434)
//			elementDelta = current.changed(element, IJavaElementDelta.F_CONTENT);
//		}
//		if (!ExternalFoldersManager.isInternalPathForExternalFolder(delta.getFullPath()))
//			elementDelta.addResourceDelta(delta);
        throw new UnsupportedOperationException();
    }

    /*
     * Returns the old root info for the given path and project.
     */
    private RootInfo oldRootInfo(IPath path, JavaProject project) {
        RootInfo oldInfo = (RootInfo)this.state.oldRoots.get(path);
        if (oldInfo == null)
            return null;
        if (oldInfo.project.equals(project))
            return oldInfo;
        ArrayList oldInfos = (ArrayList)this.state.oldOtherRoots.get(path);
        if (oldInfos == null)
            return null;
        for (int i = 0, length = oldInfos.size(); i < length; i++) {
            oldInfo = (RootInfo)oldInfos.get(i);
            if (oldInfo.project.equals(project))
                return oldInfo;
        }
        return null;
    }

    /*
     * Returns the other root infos for the given path. Look in the old other roots table if kind is REMOVED.
     */
    private ArrayList otherRootsInfo(IPath path, int kind) {
        if (kind == IResourceDelta.REMOVED) {
            return (ArrayList)this.state.oldOtherRoots.get(path);
        }
        return (ArrayList)this.state.otherRoots.get(path);
    }

    private OutputsInfo outputsInfo(RootInfo rootInfo, File res) {
//		try {
//			JavaProject proj =
//				rootInfo == null ?
//					(JavaProject)createElement(res.getProject(), IJavaElement.JAVA_PROJECT, null) :
//					rootInfo.project;
//			if (proj != null) {
//				IPath projectOutput = proj.getOutputLocation();
//				int traverseMode = IGNORE;
//				if (proj.getProject().getFullPath().equals(projectOutput)){ // case of proj==bin==src
//					return new OutputsInfo(new IPath[] {projectOutput}, new int[] {SOURCE}, 1);
//				}
//				IClasspathEntry[] classpath = proj.getResolvedClasspath();
//				IPath[] outputs = new IPath[classpath.length+1];
//				int[] traverseModes = new int[classpath.length+1];
//				int outputCount = 1;
//				outputs[0] = projectOutput;
//				traverseModes[0] = traverseMode;
//				for (int i = 0, length = classpath.length; i < length; i++) {
//					IClasspathEntry entry = classpath[i];
//					IPath entryPath = entry.getPath();
//					IPath output = entry.getOutputLocation();
//					if (output != null) {
//						outputs[outputCount] = output;
//						// check case of src==bin
//						if (entryPath.equals(output)) {
//							traverseModes[outputCount++] = (entry.getEntryKind() == IClasspathEntry.CPE_SOURCE) ? SOURCE : BINARY;
//						} else {
//							traverseModes[outputCount++] = IGNORE;
//						}
//					}
//
//					// check case of src==bin
//					if (entryPath.equals(projectOutput)) {
//						traverseModes[0] = (entry.getEntryKind() == IClasspathEntry.CPE_SOURCE) ? SOURCE : BINARY;
//					}
//				}
//				return new OutputsInfo(outputs, traverseModes, outputCount);
//			}
//		} catch (JavaModelException e) {
//			// java project doesn't exist: ignore
//		}
//		return null;
        throw new UnsupportedOperationException();
    }

    private void popUntilPrefixOf(IPath path) {
        while (this.currentElement != null) {
            IPath currentElementPath = null;
            if (this.currentElement instanceof IPackageFragmentRoot) {
                currentElementPath = ((IPackageFragmentRoot)this.currentElement).getPath();
            } else {
                IResource currentElementResource = this.currentElement.resource();
                if (currentElementResource != null) {
                    currentElementPath = currentElementResource.getFullPath();
                }
            }
            if (currentElementPath != null) {
                if (this.currentElement instanceof IPackageFragment
                    && ((IPackageFragment)this.currentElement).isDefaultPackage()
                    && currentElementPath.segmentCount() != path.segmentCount() - 1) {
                    // default package and path is not a direct child
                    this.currentElement = (Openable)this.currentElement.getParent();
                }
                if (currentElementPath.isPrefixOf(path)) {
                    return;
                }
            }
            this.currentElement = (Openable)this.currentElement.getParent();
        }
    }

    /*
     * Converts a <code>IResourceDelta</code> rooted in a <code>Workspace</code> into
     * the corresponding set of <code>IJavaElementDelta</code>, rooted in the
     * relevant <code>JavaModel</code>s.
     */
    private IJavaElementDelta processResourceDelta(IResourceDelta changes) {

        try {
            IJavaModel model = this.manager.getJavaModel();
            if (!model.isOpen()) {
                // force opening of java model so that java element delta are reported
                try {
                    model.open(null);
                } catch (JavaModelException e) {
                    if (VERBOSE) {
                        e.printStackTrace();
                    }
                    return null;
                }
            }
            this.state.initializeRoots(false/*not initiAfterLoad*/);
            this.currentElement = null;

            // get the workspace delta, and start processing there.
//			IResourceDelta[] deltas = (IResourceDelta[])changes.getAffectedChildren(IResourceDelta.ADDED | IResourceDelta.REMOVED |
// IResourceDelta.CHANGED, IContainer.INCLUDE_HIDDEN);
//			for (int i = 0; i < deltas.length; i++) {
//				IResourceDelta delta = deltas[i];
//				File res = delta.getFile();
//
//				// find out the element type
//				RootInfo rootInfo = null;
//				int elementType;
//				IProject proj = (IProject)res;
//				boolean wasJavaProject = this.state.findJavaProject(proj.getName()) != null;
//				boolean isJavaProject = JavaProject.hasJavaNature(proj);
//				if (!wasJavaProject && !isJavaProject) {
//					elementType = NON_JAVA_RESOURCE;
//				} else {
//					IPath rootPath = externalPath(res);
//					rootInfo = enclosingRootInfo(rootPath, delta.getKind());
//					if (rootInfo != null && rootInfo.isRootOfProject(rootPath)) {
//						elementType = IJavaElement.PACKAGE_FRAGMENT_ROOT;
//					} else {
//						elementType = IJavaElement.JAVA_PROJECT;
//					}
//				}
//
//				// traverse delta
//				traverseDelta(changes, IJavaElement.JAVA_PROJECT, null, null);
            updateCurrentDeltaAndIndex(changes, IJavaElement.COMPILATION_UNIT, null);
//
//				if (elementType == NON_JAVA_RESOURCE
//						|| (wasJavaProject != isJavaProject && (delta.getKind()) == IResourceDelta.CHANGED)) { // project has changed
// nature (description or open/closed)
//					try {
//						// add child as non java resource
//						nonJavaResourcesChanged((JavaModel)model, delta);
//					} catch (JavaModelException e) {
//						// java model could not be opened
//					}
//				}
//
//			}
            resetProjectCaches();

            return this.currentDelta;
        } finally {
            this.currentDelta = null;
        }
    }

    /*
     * Traverse the set of projects which have changed namespace, and reset their
     * caches and their dependents
     */
    public void resetProjectCaches() {
        if (this.projectCachesToReset.size() == 0)
            return;

//		JavaModelManager.getJavaModelManager().resetJarTypeCache();

        Iterator iterator = this.projectCachesToReset.iterator();
        HashMap projectDepencies = this.state.projectDependencies;
        HashSet affectedDependents = new HashSet();
        while (iterator.hasNext()) {
            JavaProject project = (JavaProject)iterator.next();
            project.resetCaches();
            addDependentProjects(project, projectDepencies, affectedDependents);
        }
        // reset caches of dependent projects
        iterator = affectedDependents.iterator();
        while (iterator.hasNext()) {
            JavaProject project = (JavaProject)iterator.next();
            project.resetCaches();
        }

        this.projectCachesToReset.clear();
    }

    /*
     * Registers the given delta with this delta processor.
     */
    public void registerJavaModelDelta(IJavaElementDelta delta) {
        this.javaModelDeltas.add(delta);
    }

    /*
     * Removes the given element from its parents cache of children. If the
     * element does not have a parent, or the parent is not currently open,
     * this has no effect.
     */
    private void removeFromParentInfo(Openable child) {

        Openable parent = (Openable)child.getParent();
        if (parent != null && parent.isOpen()) {
            try {
                OpenableElementInfo info = (OpenableElementInfo)parent.getElementInfo();
                info.removeChild(child);
            } catch (JavaModelException e) {
                // do nothing - we already checked if open
            }
        }
    }

    /*
     * Notification that some resource changes have happened
     * on the platform, and that the Java Model should update any required
     * internal structures such that its elements remain consistent.
     * Translates <code>IResourceDeltas</code> into <code>IJavaElementDeltas</code>.
     *
     * @see IResourceDelta
     * @see IResource
     */
    public void resourceChanged(IResourceChangeEvent event) {

        int eventType = this.overridenEventType == -1 ? event.getType() : this.overridenEventType;
		IResource resource = event.getResource();
        IResourceDelta delta = (IResourceDelta)event.getDelta();

        switch (eventType) {
            case IResourceChangeEvent.PRE_DELETE:
//				try {
//					if(resource.getType() == IResource.PROJECT
//						/*&& ((IProject) resource).hasNature(JavaCore.NATURE_ID)*/) {
//
//						deleting((IProject)resource);
//					}
//				} catch(CoreException e){
//					// project doesn't exist or is not open: ignore
//				}
                return;

            case IResourceChangeEvent.PRE_REFRESH:
//				IProject[] projects = null;
//				Object o = event.getSource();
//				if (o instanceof IProject) {
//					projects = new IProject[] { (IProject) o };
//				} else if (o instanceof IWorkspace) {
//					// https://bugs.eclipse.org/bugs/show_bug.cgi?id=261594. The single workspace refresh
//					// notification we see, implies that all projects are about to be refreshed.
//					 projects = ((IWorkspace) o).getRoot().getProjects(IContainer.INCLUDE_HIDDEN);
//				}
//				//https://bugs.eclipse.org/bugs/show_bug.cgi?id=302295
//				// Refresh all project references together in a single job
//				JavaModelManager.getExternalManager().refreshReferences(projects, null);
//
//				IJavaProject[] javaElements = new IJavaProject[projects.length];
//				for (int index = 0; index < projects.length; index++) {
//					javaElements[index] = JavaCore.create(projects[index]);
//				}
//				try {
//					checkExternalArchiveChanges(javaElements, true, null);
//				} catch (JavaModelException e) {
//		        	if (!e.isDoesNotExist())
//		        		Util.log(e, "Exception while updating external archives"); //$NON-NLS-1$
//				}
                return;

            case IResourceChangeEvent.POST_CHANGE:
                HashSet elementsToRefresh = this.state.removeExternalElementsToRefresh();
                if (isAffectedBy(delta) // avoid populating for SYNC or MARKER deltas
                    || elementsToRefresh != null) {
                    try {
                        try {
                            stopDeltas();
                            checkProjectsAndClasspathChanges(delta);

                            // generate external archive change deltas
                            if (elementsToRefresh != null) {
                                createExternalArchiveDelta(elementsToRefresh, null);
                            }

                            // generate classpath change deltas
                            HashMap classpathChanges = this.state.removeAllClasspathChanges();
//							if (classpathChanges.size() > 0) {
//								boolean hasDelta = this.currentDelta != null;
//								JavaElementDelta javaDelta = currentDelta();
//								Iterator changes = classpathChanges.values().iterator();
//								while (changes.hasNext()) {
//									ClasspathChange change = (ClasspathChange) changes.next();
//									int result = change.generateDelta(javaDelta, false/*don't add classpath change*/);
//									if ((result & ClasspathChange.HAS_DELTA) != 0) {
//										hasDelta = true;
//
//										// need to recompute root infos
//										this.state.rootsAreStale = true;
//
//										change.requestIndexing();
//										this.state.addClasspathValidation(change.project);
//									}
//									if ((result & ClasspathChange.HAS_PROJECT_CHANGE) != 0) {
//										this.state.addProjectReferenceChange(change.project, change.oldResolvedClasspath);
//									}
//									if ((result & ClasspathChange.HAS_LIBRARY_CHANGE) != 0) {
//										this.state.addExternalFolderChange(change.project, change.oldResolvedClasspath);
//									}
//								}
//								// process late coming external elements to refresh (see https://bugs.eclipse.org/bugs/show_bug
// .cgi?id=212769 )
//								elementsToRefresh = this.state.removeExternalElementsToRefresh();
//								if (elementsToRefresh != null) {
//									hasDelta |= createExternalArchiveDelta(elementsToRefresh, null);
//								}
//								if (!hasDelta)
//									this.currentDelta = null;
//							}

                            // generate Java deltas from resource changes
                            IJavaElementDelta translatedDelta = processResourceDelta(delta);
                            if (translatedDelta != null) {
                                registerJavaModelDelta(translatedDelta);
                            }
                        } finally {
                            this.sourceElementParserCache = null; // don't hold onto parser longer than necessary
                            startDeltas();
                        }
                        IElementChangedListener[] listeners;
                        int listenerCount;
                        synchronized (this.state) {
                            listeners = this.state.elementChangedListeners;
                            listenerCount = this.state.elementChangedListenerCount;
                        }
                        notifyTypeHierarchies(listeners, listenerCount);
                        fire(null, ElementChangedEvent.POST_CHANGE);
                    } finally {
                        // workaround for bug 15168 circular errors not reported
                        this.state.resetOldJavaProjectNames();
                        this.oldRoots = null;
                    }
                }
                return;

            case IResourceChangeEvent.PRE_BUILD:
//				// force initialization of roots before builders run to avoid deadlock in another thread
//				// (note this is no-op if already initialized)
//				// see https://bugs.eclipse.org/bugs/show_bug.cgi?id=241751
//				this.state.initializeRoots(false/*not initiAfterLoad*/);
//
//				boolean isAffected = isAffectedBy(delta);
//				boolean needCycleValidation = isAffected && validateClasspaths(delta);
//
//				// update external folders if necessary
//			    ExternalFolderChange[] folderChanges = this.state.removeExternalFolderChanges();
//				if (folderChanges != null) {
//				    for (int i = 0, length = folderChanges.length; i < length; i++) {
//				        try {
//					        folderChanges[i].updateExternalFoldersIfNecessary(false/*do not refresh since we are not in the thread that
// added the external folder to the classpath*/, null);
//				        } catch (JavaModelException e) {
//				        	if (!e.isDoesNotExist())
//				        		Util.log(e, "Exception while updating external folders"); //$NON-NLS-1$
//				        }
//				    }
//				}
//
//				// create classpath markers if necessary
//				ClasspathValidation[] validations = this.state.removeClasspathValidations();
//				if (validations != null) {
//					for (int i = 0, length = validations.length; i < length; i++) {
//						ClasspathValidation validation = validations[i];
//						validation.validate();
//					}
//				}
//
//				// update project references if necessary
//			    ProjectReferenceChange[] projectRefChanges = this.state.removeProjectReferenceChanges();
//				if (projectRefChanges != null) {
//				    for (int i = 0, length = projectRefChanges.length; i < length; i++) {
//				        try {
//					        projectRefChanges[i].updateProjectReferencesIfNecessary();
//				        } catch(JavaModelException e) {
//				            // project doesn't exist any longer, continue with next one
//				        	if (!e.isDoesNotExist())
//				        		Util.log(e, "Exception while updating project references"); //$NON-NLS-1$
//				        }
//				    }
//				}
//
//				if (needCycleValidation || projectRefChanges != null) {
//					// update all cycle markers since the project references changes may have affected cycles
//					try {
//						JavaProject.validateCycles(null);
//					} catch (JavaModelException e) {
//						// a project no longer exists
//					}
//				}
//
//				if (isAffected) {
//					Object source = event.getSource();
//					projects = null;
//					if (source instanceof IWorkspace) {
//						 projects = ((IWorkspace) source).getRoot().getProjects();
//					} else if (source instanceof IProject) {
//						projects = new IProject[] {(IProject) source};
//					} else {
//						Util.log(new Exception(),
//								 "Expected to see a workspace or project on the PRE_BUILD resource change but was: " + source.toString());
// $NON-NLS-1$
//					}
//					if (projects != null) {
//						// If we are about to do a build and a Java project's first builder is not the Java builder,
//						// then it is possible that one of the earlier builders will build a jar file that is on that
//						// project's classpath. If we see that, then to be safe we must flush the caching of the
//						// JavaModelManager's external file state.
//						// A possible further optimization for this situation where earlier builders can affect the
//						// Java builder would be to add a new classpath element attribute that identifies whether
//						// or not a library jar is "stable" and needs to be flushed.
//						for (int i = 0; i < projects.length; i++) {
//							try {
//								IProject project = projects[i];
//								if (project.isOpen() && project.hasNature(JavaCore.NATURE_ID)) {
//									IBuildConfiguration[] configs = project.getBuildConfigs();
//									if (configs.length > 1 && !JavaCore.BUILDER_ID.equals(configs[0].getName())) {
//										this.manager.resetExternalFilesCache();
//										break;
//									}
//								}
//							} catch (CoreException exception) {
//				        		Util.log(exception, "Exception while checking builder configuration ordering"); //$NON-NLS-1$
//							}
//						 }
//					}
//					JavaBuilder.buildStarting();
//				}

                // does not fire any deltas
                return;

            case IResourceChangeEvent.POST_BUILD:
//                JavaBuilder.buildFinished();
                return;
        }
    }

    /*
     * Returns the root info for the given path. Look in the old roots table if kind is REMOVED.
     */
    private RootInfo rootInfo(IPath path, int kind) {
        if (kind == IResourceDelta.REMOVED) {
            return (RootInfo)this.state.oldRoots.get(path);
        }
        return (RootInfo)this.state.roots.get(path);
    }

    /*
     * Turns the firing mode to on. That is, deltas that are/have been
     * registered will be fired.
     */
    private void startDeltas() {
        this.isFiring = true;
    }

    /*
     * Turns the firing mode to off. That is, deltas that are/have been
     * registered will not be fired until deltas are started again.
     */
    private void stopDeltas() {
        this.isFiring = false;
    }

    /*
     * Converts an <code>IResourceDelta</code> and its children into
     * the corresponding <code>IJavaElementDelta</code>s.
     */
    private void traverseDelta(
            IResourceDelta delta,
            int elementType,
            RootInfo rootInfo,
            OutputsInfo outputsInfo) {

        IResource res = delta.getResource();

        // set stack of elements
        if (this.currentElement == null && rootInfo != null) {
            this.currentElement = rootInfo.project;
        }

        // process current delta
        boolean processChildren = true;
        if (res instanceof IProject) {
            // reset source element parser cache
            this.sourceElementParserCache = null;

            processChildren =
                    updateCurrentDeltaAndIndex(
                            delta,
                            elementType == IJavaElement.PACKAGE_FRAGMENT_ROOT ?
                            IJavaElement.JAVA_PROJECT : // case of prj=src
                            elementType,
                            rootInfo);
        } else if (rootInfo != null) {
            processChildren = updateCurrentDeltaAndIndex(delta, elementType, rootInfo);
        } else {
            // not yet inside a package fragment root
            processChildren = true;
        }

        // get the project's output locations and traverse mode
//		if (outputsInfo == null) outputsInfo = outputsInfo(rootInfo, res);

        // process children if needed
        if (processChildren) {
            IResourceDelta[] children = (IResourceDelta[])delta.getAffectedChildren();
            boolean oneChildOnClasspath = false;
            int length = children.length;
            IResourceDelta[] orphanChildren = null;
            Openable parent = null;
            boolean isValidParent = true;
            for (int i = 0; i < length; i++) {
                IResourceDelta child = children[i];
                IResource childRes = child.getResource();

                // check source attachment change
                checkSourceAttachmentChange(child, childRes);

                // find out whether the child is a package fragment root of the current project
                IPath childPath = externalPath(childRes);
                int childKind = child.getKind();
                RootInfo childRootInfo = rootInfo(childPath, childKind);
                RootInfo originalChildRootInfo = childRootInfo;
                if (childRootInfo != null && !childRootInfo.isRootOfProject(childPath)) {
                    // package fragment root of another project (dealt with later)
                    childRootInfo = null;
                }

                // compute child type
                int childType =
                        elementType(
                                childRes,
                                childKind,
                                elementType,
                                rootInfo == null ? childRootInfo : rootInfo
                                   );

                // is childRes in the output folder and is it filtered out ?
                boolean isResFilteredFromOutput = isResFilteredFromOutput(rootInfo, outputsInfo, childRes, childType);

                boolean isNestedRoot = rootInfo != null && childRootInfo != null;
                if (!isResFilteredFromOutput
                    && !isNestedRoot) { // do not treat as non-java rsc if nested root

                    traverseDelta(child, childType, rootInfo == null ? childRootInfo : rootInfo,
                                  outputsInfo); // traverse delta for child in the same project

                    if (childType == NON_JAVA_RESOURCE) {
                        if (rootInfo != null) { // if inside a package fragment root
                            if (!isValidParent) continue;
                            if (parent == null) {
                                // find the parent of the non-java resource to attach to
                                if (this.currentElement == null
                                    || !rootInfo.project.equals(this.currentElement
                                                                        .getJavaProject())) { // note if currentElement is the
                                                                        // IJavaModel, getJavaProject() is null
                                    // force the currentProject to be used
                                    this.currentElement = rootInfo.project;
                                }
                                if (elementType == IJavaElement.JAVA_PROJECT
                                    || (elementType == IJavaElement.PACKAGE_FRAGMENT_ROOT
                                        && res instanceof IProject)) {
                                    // NB: attach non-java resource to project (not to its package fragment root)
                                    parent = rootInfo.project;
                                } else {
                                    parent = createElement(res, elementType, rootInfo);
                                }
                                if (parent == null) {
                                    isValidParent = false;
                                    continue;
                                }
                            }
                            // add child as non java resource
                            try {
                                nonJavaResourcesChanged(parent, child);
                            } catch (JavaModelException e) {
                                // ignore
                            }
                        } else {
                            // the non-java resource (or its parent folder) will be attached to the java project
                            if (orphanChildren == null) orphanChildren = new IResourceDelta[length];
                            orphanChildren[i] = child;
                        }
                    } else {
                        if (rootInfo == null && childRootInfo == null) {
                            // the non-java resource (or its parent folder) will be attached to the java project
                            if (orphanChildren == null) orphanChildren = new IResourceDelta[length];
                            orphanChildren[i] = child;
                        }
                    }
                } else {
                    oneChildOnClasspath = true; // to avoid reporting child delta as non-java resource delta
                }

                // if child is a nested root
                // or if it is not a package fragment root of the current project
                // but it is a package fragment root of another project, traverse delta too
                if (isNestedRoot
                    || (childRootInfo == null && originalChildRootInfo != null)) {
                    traverseDelta(child, IJavaElement.PACKAGE_FRAGMENT_ROOT, originalChildRootInfo,
                                  null); // binary output of childRootInfo.project cannot be this root
                }

                // if the child is a package fragment root of one or several other projects
                ArrayList rootList;
                if ((rootList = otherRootsInfo(childPath, childKind)) != null) {
                    Iterator iterator = rootList.iterator();
                    while (iterator.hasNext()) {
                        originalChildRootInfo = (RootInfo)iterator.next();
                        this.currentElement =
                                null; // ensure that 2 roots refering to the same resource don't share the current element (see
                                // https://bugs.eclipse.org/bugs/show_bug.cgi?id=210746 )
                        traverseDelta(child, IJavaElement.PACKAGE_FRAGMENT_ROOT, originalChildRootInfo,
                                      null); // binary output of childRootInfo.project cannot be this root
                    }
                }
            }
            if (orphanChildren != null
                && (oneChildOnClasspath // orphan children are siblings of a package fragment root
                    || res instanceof IProject)) { // non-java resource directly under a project

//				// attach orphan children
//				IProject rscProject = res.getProject();
//				JavaProject adoptiveProject = (JavaProject)JavaCore.create(rscProject);
//				if (adoptiveProject != null
//						&& JavaProject.hasJavaNature(rscProject)) { // delta iff Java project (18698)
//					for (int i = 0; i < length; i++) {
//						if (orphanChildren[i] != null) {
//							try {
//								nonJavaResourcesChanged(adoptiveProject, orphanChildren[i]);
//							} catch (JavaModelException e) {
//								// ignore
//							}
//						}
//					}
//				}
            } // else resource delta will be added by parent
        } // else resource delta will be added by parent
    }

    private void validateClasspaths(IResourceDelta delta, HashSet affectedProjects) {
//		IResource resource = delta.getResource();
//		boolean processChildren = false;
//		switch (resource.getType()) {
//			case IResource.ROOT :
//				if (delta.getKind() == IResourceDelta.CHANGED) {
//					processChildren = true;
//				}
//				break;
//			case IResource.PROJECT :
//				IProject project = (IProject)resource;
//				int kind = delta.getKind();
//				boolean isJavaProject = JavaProject.hasJavaNature(project);
//				switch (kind) {
//					case IResourceDelta.ADDED:
//						processChildren = isJavaProject;
//						affectedProjects.add(project.getFullPath());
//						break;
//					case IResourceDelta.CHANGED:
//						processChildren = isJavaProject;
//						if ((delta.getFlags() & IResourceDelta.OPEN) != 0) {
//							// project opened or closed
//							if (isJavaProject) {
//								JavaProject javaProject = (JavaProject)JavaCore.create(project);
//								this.state.addClasspathValidation(javaProject); // in case .classpath got modified while closed
//							}
//							affectedProjects.add(project.getFullPath());
//						} else if ((delta.getFlags() & IResourceDelta.DESCRIPTION) != 0) {
//							boolean wasJavaProject = this.state.findJavaProject(project.getName()) != null;
//							if (wasJavaProject  != isJavaProject) {
//								// project gained or lost Java nature
//								JavaProject javaProject = (JavaProject)JavaCore.create(project);
//								this.state.addClasspathValidation(javaProject); // add/remove classpath markers
//								affectedProjects.add(project.getFullPath());
//							}
//						}
//						break;
//					case IResourceDelta.REMOVED:
//						affectedProjects.add(project.getFullPath());
//						break;
//				}
//				break;
//			case IResource.FILE :
//				/* check classpath or prefs files change */
//				IFile file = (IFile) resource;
//				String fileName = file.getName();
//				RootInfo rootInfo = null;
//				// https://bugs.eclipse.org/bugs/show_bug.cgi?id=229042
//				// Mark a validation if a library with package fragment root in the project has changed
//				if (fileName.equals(JavaProject.CLASSPATH_FILENAME)
//						|| ((rootInfo = rootInfo(file.getFullPath(), delta.getKind())) != null && rootInfo.entryKind == IClasspathEntry
// .CPE_LIBRARY)) {
//					JavaProject javaProject = (JavaProject)JavaCore.create(file.getProject());
//					this.state.addClasspathValidation(javaProject);
//					affectedProjects.add(file.getProject().getFullPath());
//				}
//				break;
//		}
//		if (processChildren) {
//			IResourceDelta[] children = delta.getAffectedChildren();
//			for (int i = 0; i < children.length; i++) {
//				validateClasspaths(children[i], affectedProjects);
//			}
//		}
    }

    /*
     * Update the current delta (i.e. add/remove/change the given element) and update the corresponding index.
     * Returns whether the children of the given delta must be processed.
     * @throws a JavaModelException if the delta doesn't correspond to a java element of the given type.
     */
    public boolean updateCurrentDeltaAndIndex(IResourceDelta delta, int elementType, RootInfo rootInfo) {
        Openable element;
        switch (delta.getKind()) {
            case IResourceDelta.ADDED:
                IResource deltaRes = delta.getResource();
                element = createElement(deltaRes, elementType, rootInfo);
                if (element == null) {
                    // resource might be containing shared roots (see bug 19058)
                    this.state.updateRoots(deltaRes.getFullPath(), delta, this);
                    return rootInfo != null && rootInfo.inclusionPatterns != null;
                }
                updateIndex(element, delta);
                elementAdded(element, delta, rootInfo);
//				if (elementType == IJavaElement.PACKAGE_FRAGMENT_ROOT)
//					this.state.addClasspathValidation(rootInfo.project);
                return elementType == IJavaElement.PACKAGE_FRAGMENT;
            case IResourceDelta.REMOVED:
                deltaRes = delta.getResource();
                element = createElement(deltaRes, elementType, rootInfo);
                if (element == null) {
                    // resource might be containing shared roots (see bug 19058)
                    this.state.updateRoots(deltaRes.getFullPath(), delta, this);
                    return rootInfo != null && rootInfo.inclusionPatterns != null;
                }
                updateIndex(element, delta);
                elementRemoved(element, delta, rootInfo);
//                if (elementType == IJavaElement.PACKAGE_FRAGMENT_ROOT)
//					this.state.addClasspathValidation(rootInfo.project);

//				if (deltaRes.getType() == IResource.PROJECT){
//					// reset the corresponding project built state, since cannot reuse if added back
//					if (JavaBuilder.DEBUG)
//						System.out.println("Clearing last state for removed project : " + deltaRes); //$NON-NLS-1$
//					this.manager.setLastBuiltState((IProject)deltaRes, null /*no state*/);
//
//					// clean up previous session containers (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=89850)
//					this.manager.previousSessionContainers.remove(element);
//				}
                    return elementType == IJavaElement.PACKAGE_FRAGMENT;
            case IResourceDelta.CHANGED:
                int flags = delta.getFlags();
                if (elementType == IJavaElement.PACKAGE_FRAGMENT_ROOT && (flags & IResourceDelta.LOCAL_CHANGED) != 0) {
                    // external folder added or removed
                    if (oldRootInfo(rootInfo.rootPath, rootInfo.project) == null) {
                        // root just added to the classpath
                        break;
                    }
                    deltaRes = delta.getResource();
                    Object target = JavaModel.getExternalTarget(deltaRes.getFullPath(), true/*check resource existence*/);
                    element = createElement(deltaRes, elementType, rootInfo);
                    updateIndex(element, delta);
                    if (target != null) {
                        // external folder added
                        elementAdded(element, delta, rootInfo);
                    } else {
                        // external folder removed
                        elementRemoved(element, delta, rootInfo);
                    }
//					this.state.addClasspathValidation(rootInfo.project);
                } else if ((flags & IResourceDelta.CONTENT) != 0 || (flags & IResourceDelta.ENCODING) != 0) {
                    // content or encoding has changed
                    element = createElement(delta.getResource(), elementType, rootInfo);
                    if (element == null) return false;
                    updateIndex(element, delta);
                    contentChanged(element);
                } else if (elementType == IJavaElement.JAVA_PROJECT) {
//					if ((flags & IResourceDelta.OPEN) != 0) {
//						// project has been opened or closed
//						IProject res = (IProject)delta.getResource();
//						element = createElement(res, elementType, rootInfo);
//						if (element == null) {
//							// resource might be containing shared roots (see bug 19058)
//							this.state.updateRoots(res.getFullPath(), delta, this);
//							return false;
//						}
//						if (res.isOpen()) {
//							if (JavaProject.hasJavaNature(res)) {
//								addToParentInfo(element);
//								currentDelta().opened(element);
//								this.state.updateRoots(element.getPath(), delta, this);
//
//								// remember that the project's cache must be reset
//								this.projectCachesToReset.add(element);
//
//								this.manager.indexManager.indexAll(res);
//							}
//						} else {
//							boolean wasJavaProject = this.state.findJavaProject(res.getName()) != null;
//							if (wasJavaProject) {
//								close(element);
//								removeFromParentInfo(element);
//								currentDelta().closed(element);
//								this.manager.indexManager.discardJobs(element.getElementName());
//								this.manager.indexManager.removeIndexFamily(res.getFullPath());
//							}
//						}
//						return false; // when a project is open/closed don't process children
//					}
//					if ((flags & IResourceDelta.DESCRIPTION) != 0) {
//						IProject res = (IProject)delta.getResource();
//						boolean wasJavaProject = this.state.findJavaProject(res.getName()) != null;
//						boolean isJavaProject = JavaProject.hasJavaNature(res);
//						if (wasJavaProject != isJavaProject) {
//							// project's nature has been added or removed
//							element = createElement(res, elementType, rootInfo);
//							if (element == null) return false; // note its resources are still visible as roots to other projects
//							if (isJavaProject) {
//								elementAdded(element, delta, rootInfo);
//								this.manager.indexManager.indexAll(res);
//							} else {
//								elementRemoved(element, delta, rootInfo);
//								this.manager.indexManager.discardJobs(element.getElementName());
//								this.manager.indexManager.removeIndexFamily(res.getFullPath());
//								// reset the corresponding project built state, since cannot reuse if added back
//								if (JavaBuilder.DEBUG)
//									System.out.println("Clearing last state for project loosing Java nature: " + res); //$NON-NLS-1$
//								this.manager.setLastBuiltState(res, null /*no state*/);
//							}
//							return false; // when a project's nature is added/removed don't process children
//						}
//					}
                }
                return true;
        }
        return true;
    }

    private void updateIndex(Openable element, IResourceDelta delta) {

        IndexManager indexManager = this.manager.indexManager;
        if (indexManager == null)
            return;

        switch (element.getElementType()) {
            case IJavaElement.JAVA_PROJECT:
                switch (delta.getKind()) {
                    case IResourceDelta.ADDED:
                        indexManager.indexAll(element.getJavaProject().getProject());
                        break;
                    case IResourceDelta.REMOVED:
                        indexManager.removeIndexFamily(element.getJavaProject().getProject().getFullPath());
                        // NB: Discarding index jobs belonging to this project was done during PRE_DELETE
                        break;
                    // NB: Update of index if project is opened, closed, or its java nature is added or removed
                    //     is done in updateCurrentDeltaAndIndex
                }
                break;
            case IJavaElement.PACKAGE_FRAGMENT_ROOT:
                if (element instanceof JarPackageFragmentRoot) {
                    JarPackageFragmentRoot root = (JarPackageFragmentRoot)element;
                    // index jar file only once (if the root is in its declaring project)
                    IPath jarPath = root.getPath();
                    switch (delta.getKind()) {
                        case IResourceDelta.ADDED:
                            // index the new jar
                            indexManager.indexLibrary(jarPath, root.getJavaProject().getProject(), root.getIndexPath());
                            break;
                        case IResourceDelta.CHANGED:
                            // first remove the index so that it is forced to be re-indexed
                            indexManager.removeIndex(jarPath);
                            // then index the jar
                            indexManager.indexLibrary(jarPath, root.getJavaProject().getProject(), root.getIndexPath());
                            break;
                        case IResourceDelta.REMOVED:
                            // the jar was physically removed: remove the index
                            indexManager.discardJobs(jarPath.toString());
                            indexManager.removeIndex(jarPath);
                            break;
                    }
                    break;
                }
                int kind = delta.getKind();
                if (kind == IResourceDelta.ADDED || kind == IResourceDelta.REMOVED ||
                    (kind == IResourceDelta.CHANGED && (delta.getFlags() & IResourceDelta.LOCAL_CHANGED) != 0)) {
                    PackageFragmentRoot root = (PackageFragmentRoot)element;
                    updateRootIndex(root, CharOperation.NO_STRINGS, delta);
                    break;
                }
                // don't break as packages of the package fragment root can be indexed below
                // $FALL-THROUGH$
            case IJavaElement.PACKAGE_FRAGMENT:
                switch (delta.getKind()) {
                    case IResourceDelta.CHANGED:
                        if ((delta.getFlags() & IResourceDelta.LOCAL_CHANGED) == 0)
                            break;
                        // $FALL-THROUGH$
                    case IResourceDelta.ADDED:
                    case IResourceDelta.REMOVED:
                        IPackageFragment pkg = null;
                        if (element instanceof IPackageFragmentRoot) {
                            PackageFragmentRoot root = (PackageFragmentRoot)element;
                            pkg = root.getPackageFragment(CharOperation.NO_STRINGS);
                        } else {
                            pkg = (IPackageFragment)element;
                        }
                        RootInfo rootInfo = rootInfo(pkg.getParent().getPath(), delta.getKind());
                        boolean isSource =
                                rootInfo == null // if null, defaults to source
                                || rootInfo.entryKind == IClasspathEntry.CPE_SOURCE;
                        IResourceDelta[] children = (IResourceDelta[])delta.getAffectedChildren();
                        for (int i = 0, length = children.length; i < length; i++) {
                            IResourceDelta child = children[i];
                            IResource resource = child.getResource();
                            // TODO (philippe) Why do this? Every child is added anyway as the delta is walked
                            if (resource instanceof IFile) {
                                String name = resource.getName();
                                if (isSource) {
                                    if (org.eclipse.jdt.internal.core.util.Util.isJavaLikeFileName(name)) {
                                        Openable cu = (Openable)pkg.getCompilationUnit(name);
                                        updateIndex(cu, child);
                                    }
                                } else if (org.eclipse.jdt.internal.compiler.util.Util.isClassFileName(name)) {
                                    Openable classFile = (Openable)pkg.getClassFile(name);
                                    updateIndex(classFile, child);
                                }
                            }
                        }
                        break;
                }
                break;
            case IJavaElement.CLASS_FILE:
//				IFile file = (IFile) delta.getResource();
//				IJavaProject project = element.getJavaProject();
//				PackageFragmentRoot root = element.getPackageFragmentRoot();
//				IPath binaryFolderPath = root.isExternal() && !root.isArchive() ? root.resource().getFullPath() : root.getPath();
//				// if the class file is part of the binary output, it has been created by
//				// the java builder -> ignore
//				try {
//					if (binaryFolderPath.equals(project.getOutputLocation())) {
//						break;
//					}
//				} catch (JavaModelException e) {
//					// project doesn't exist: ignore
//				}
//				switch (delta.getKind()) {
//					case IResourceDelta.CHANGED :
//						// no need to index if the content has not changed
//						int flags = delta.getFlags();
//						if ((flags & IResourceDelta.CONTENT) == 0 && (flags & IResourceDelta.ENCODING) == 0)
//							break;
//						// $FALL-THROUGH$
//					case IResourceDelta.ADDED :
//						indexManager.addBinary(file, binaryFolderPath);
//						break;
//					case IResourceDelta.REMOVED :
//						String containerRelativePath = Util.relativePath(file.getFullPath(), binaryFolderPath.segmentCount());
//						indexManager.remove(containerRelativePath, binaryFolderPath);
//						break;
//				}
                break;
            case IJavaElement.COMPILATION_UNIT:
                IFile file = (IFile)delta.getResource();
                switch (delta.getKind()) {
                    case IResourceDelta.CHANGED:
                        // no need to index if the content has not changed
                        int flags = delta.getFlags();
                        if ((flags & IResourceDelta.CONTENT) == 0 && (flags & IResourceDelta.ENCODING) == 0)
                            break;
                        // $FALL-THROUGH$
                    case IResourceDelta.ADDED:
                        indexManager.addSource(file, element.getJavaProject().getPath(), getSourceElementParser(element));
                        // Clean file from secondary types cache but do not update indexing secondary type cache as it will be updated through indexing itself
                        this.manager.secondaryTypesRemoving(file, false);
                        break;
                    case IResourceDelta.REMOVED:
                        indexManager.remove(Util.relativePath(file.getFullPath(), 1/*remove project segment*/),
                                            element.getJavaProject().getPath());
                        // Clean file from secondary types cache and update indexing secondary type cache as indexing cannot remove secondary types from cache
                        this.manager.secondaryTypesRemoving(file, true);
                        break;
                }
        }
    }

//	/*
//	 * Validate the classpaths of the projects affected by the given delta.
//	 * Create markers if necessary.
//	 * Returns whether cycle markers should be recomputed.
//	 */
//	private boolean validateClasspaths(IResourceDelta delta) {
//		HashSet affectedProjects = new HashSet(5);
//		validateClasspaths(delta, affectedProjects);
//		boolean needCycleValidation = false;
//
//		// validate classpaths of affected projects (dependent projects
//		// or projects that reference a library in one of the projects that have changed)
//		if (!affectedProjects.isEmpty()) {
//			IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot();
//			IProject[] projects = workspaceRoot.getProjects();
//			int length = projects.length;
//			for (int i = 0; i < length; i++){
//				IProject project = projects[i];
//				JavaProject javaProject = (JavaProject)JavaCore.create(project);
//				try {
//					IPath projectPath = project.getFullPath();
//					IClasspathEntry[] classpath = javaProject.getResolvedClasspath(); // allowed to reuse model cache
//					for (int j = 0, cpLength = classpath.length; j < cpLength; j++) {
//						IClasspathEntry entry = classpath[j];
//						switch (entry.getEntryKind()) {
//							case IClasspathEntry.CPE_PROJECT:
//								if (affectedProjects.contains(entry.getPath())) {
//									this.state.addClasspathValidation(javaProject);
//									needCycleValidation = true;
//								}
//								break;
//							case IClasspathEntry.CPE_LIBRARY:
//								IPath entryPath = entry.getPath();
//								IPath libProjectPath = entryPath.removeLastSegments(entryPath.segmentCount()-1);
//								if (!libProjectPath.equals(projectPath) // if library contained in another project
//										&& affectedProjects.contains(libProjectPath)) {
//									this.state.addClasspathValidation(javaProject);
//								}
//								break;
//						}
//					}
//				} catch(JavaModelException e) {
//						// project no longer exists
//				}
//			}
//		}
//		return needCycleValidation;
//	}

    /*
     * Update Java Model given some delta
     */
    public void updateJavaModel(IJavaElementDelta customDelta) {

        if (customDelta == null) {
            for (int i = 0, length = this.javaModelDeltas.size(); i < length; i++) {
                IJavaElementDelta delta = (IJavaElementDelta)this.javaModelDeltas.get(i);
                this.modelUpdater.processJavaDelta(delta);
            }
        } else {
            this.modelUpdater.processJavaDelta(customDelta);
        }
    }

    /*
     * Updates the index of the given root (assuming it's an addition or a removal).
     * This is done recusively, pkg being the current package.
     */
    private void updateRootIndex(PackageFragmentRoot root, String[] pkgName, IResourceDelta delta) {
        Openable pkg = root.getPackageFragment(pkgName);
        updateIndex(pkg, delta);
        IResourceDelta[] children = (IResourceDelta[])delta.getAffectedChildren();
        for (int i = 0, length = children.length; i < length; i++) {
            IResourceDelta child = children[i];
            IResource resource = child.getResource();
            if (resource instanceof IFolder) {
                String[] subpkgName = Util.arrayConcat(pkgName, resource.getName());
                updateRootIndex(root, subpkgName, child);
            }
        }
    }

    /*
     * An object to hold information about a project's output folders (where .class files are generated).
     */
    static class OutputsInfo {
        int     outputCount;
        IPath[] paths;
        int[]   traverseModes;

        OutputsInfo(IPath[] paths, int[] traverseModes, int outputCount) {
            this.paths = paths;
            this.traverseModes = traverseModes;
            this.outputCount = outputCount;
        }

        public String toString() {
            if (this.paths == null) return "<none>"; //$NON-NLS-1$
            StringBuffer buffer = new StringBuffer();
            for (int i = 0; i < this.outputCount; i++) {
                buffer.append("path="); //$NON-NLS-1$
                buffer.append(this.paths[i].toString());
                buffer.append("\n->traverse="); //$NON-NLS-1$
                switch (this.traverseModes[i]) {
                    case BINARY:
                        buffer.append("BINARY"); //$NON-NLS-1$
                        break;
                    case IGNORE:
                        buffer.append("IGNORE"); //$NON-NLS-1$
                        break;
                    case SOURCE:
                        buffer.append("SOURCE"); //$NON-NLS-1$
                        break;
                    default:
                        buffer.append("<unknown>"); //$NON-NLS-1$
                }
                if (i + 1 < this.outputCount) {
                    buffer.append('\n');
                }
            }
            return buffer.toString();
        }
    }

    /*
     * An object to hold information about IPackageFragmentRoots (which correspond to
     * individual classpath entry items, e.g., a java/javatests source root or library
     * archive jar.)
     */
    public static class RootInfo {
        final public JavaProject project;
        final        char[][]    inclusionPatterns;
        final        char[][]    exclusionPatterns;
        final        IPath       rootPath;
        final        int         entryKind;
        IPackageFragmentRoot root;
        IPackageFragmentRoot cache;

        RootInfo(JavaProject project, IPath rootPath, char[][] inclusionPatterns, char[][] exclusionPatterns, int entryKind) {
            this.project = project;
            this.rootPath = rootPath;
            this.inclusionPatterns = inclusionPatterns;
            this.exclusionPatterns = exclusionPatterns;
            this.entryKind = entryKind;
            this.cache = getPackageFragmentRoot();
        }

        public IPackageFragmentRoot getPackageFragmentRoot() {
            IPackageFragmentRoot tRoot = null;
			Object target = JavaModel.getTarget(this.rootPath, false/*don't check existence*/);
			if (target instanceof IResource) {
				tRoot = this.project.getPackageFragmentRoot((IResource)target);
			} else {
            tRoot = this.project.getPackageFragmentRoot(this.rootPath.toOSString());
			}
            return tRoot;
        }

        public IPackageFragmentRoot getPackageFragmentRoot(IResource resource) {
            if (this.root == null) {
                if (resource != null) {
                    this.root = this.project.getPackageFragmentRoot(resource);
                } else {
                    this.root = getPackageFragmentRoot();
                }
            }
            if (this.root != null)
                this.cache = this.root;
            return this.root;
        }

        boolean isRootOfProject(IPath path) {
            return this.rootPath.equals(path) && this.project.getProject().getFullPath().isPrefixOf(path);
        }

        public String toString() {
            StringBuffer buffer = new StringBuffer("project="); //$NON-NLS-1$
            if (this.project == null) {
                buffer.append("null"); //$NON-NLS-1$
            } else {
                buffer.append(this.project.getElementName());
            }
            buffer.append("\npath="); //$NON-NLS-1$
            if (this.rootPath == null) {
                buffer.append("null"); //$NON-NLS-1$
            } else {
                buffer.append(this.rootPath.toString());
            }
            buffer.append("\nincluding="); //$NON-NLS-1$
            if (this.inclusionPatterns == null) {
                buffer.append("null"); //$NON-NLS-1$
            } else {
                for (int i = 0, length = this.inclusionPatterns.length; i < length; i++) {
                    buffer.append(new String(this.inclusionPatterns[i]));
                    if (i < length - 1) {
                        buffer.append("|"); //$NON-NLS-1$
                    }
                }
            }
            buffer.append("\nexcluding="); //$NON-NLS-1$
            if (this.exclusionPatterns == null) {
                buffer.append("null"); //$NON-NLS-1$
            } else {
                for (int i = 0, length = this.exclusionPatterns.length; i < length; i++) {
                    buffer.append(new String(this.exclusionPatterns[i]));
                    if (i < length - 1) {
                        buffer.append("|"); //$NON-NLS-1$
                    }
                }
            }
            return buffer.toString();
        }
    }
}
